r23784: use the GPLv3 boilerplate as recommended by the FSF and the license text
[Samba/bb.git] / source / libads / sasl.c
blob9536ba31beef981112a58ede51164382753e3035
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 3 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, see <http://www.gnu.org/licenses/>.
20 #include "includes.h"
22 #ifdef HAVE_LDAP
24 /*
25 perform a LDAP/SASL/SPNEGO/NTLMSSP bind (just how many layers can
26 we fit on one socket??)
28 static ADS_STATUS ads_sasl_spnego_ntlmssp_bind(ADS_STRUCT *ads)
30 DATA_BLOB msg1 = data_blob_null;
31 DATA_BLOB blob = data_blob_null;
32 DATA_BLOB blob_in = data_blob_null;
33 DATA_BLOB blob_out = data_blob_null;
34 struct berval cred, *scred = NULL;
35 int rc;
36 NTSTATUS nt_status;
37 int turn = 1;
39 struct ntlmssp_state *ntlmssp_state;
41 if (!NT_STATUS_IS_OK(nt_status = ntlmssp_client_start(&ntlmssp_state))) {
42 return ADS_ERROR_NT(nt_status);
44 ntlmssp_state->neg_flags &= ~NTLMSSP_NEGOTIATE_SIGN;
46 if (!NT_STATUS_IS_OK(nt_status = ntlmssp_set_username(ntlmssp_state, ads->auth.user_name))) {
47 return ADS_ERROR_NT(nt_status);
49 if (!NT_STATUS_IS_OK(nt_status = ntlmssp_set_domain(ntlmssp_state, ads->auth.realm))) {
50 return ADS_ERROR_NT(nt_status);
52 if (!NT_STATUS_IS_OK(nt_status = ntlmssp_set_password(ntlmssp_state, ads->auth.password))) {
53 return ADS_ERROR_NT(nt_status);
56 blob_in = data_blob_null;
58 do {
59 nt_status = ntlmssp_update(ntlmssp_state,
60 blob_in, &blob_out);
61 data_blob_free(&blob_in);
62 if ((NT_STATUS_EQUAL(nt_status, NT_STATUS_MORE_PROCESSING_REQUIRED)
63 || NT_STATUS_IS_OK(nt_status))
64 && blob_out.length) {
65 if (turn == 1) {
66 /* and wrap it in a SPNEGO wrapper */
67 msg1 = gen_negTokenInit(OID_NTLMSSP, blob_out);
68 } else {
69 /* wrap it in SPNEGO */
70 msg1 = spnego_gen_auth(blob_out);
73 data_blob_free(&blob_out);
75 cred.bv_val = (char *)msg1.data;
76 cred.bv_len = msg1.length;
77 scred = NULL;
78 rc = ldap_sasl_bind_s(ads->ld, NULL, "GSS-SPNEGO", &cred, NULL, NULL, &scred);
79 data_blob_free(&msg1);
80 if ((rc != LDAP_SASL_BIND_IN_PROGRESS) && (rc != 0)) {
81 if (scred) {
82 ber_bvfree(scred);
85 ntlmssp_end(&ntlmssp_state);
86 return ADS_ERROR(rc);
88 if (scred) {
89 blob = data_blob(scred->bv_val, scred->bv_len);
90 ber_bvfree(scred);
91 } else {
92 blob = data_blob_null;
95 } else {
97 ntlmssp_end(&ntlmssp_state);
98 data_blob_free(&blob_out);
99 return ADS_ERROR_NT(nt_status);
102 if ((turn == 1) &&
103 (rc == LDAP_SASL_BIND_IN_PROGRESS)) {
104 DATA_BLOB tmp_blob = data_blob_null;
105 /* the server might give us back two challenges */
106 if (!spnego_parse_challenge(blob, &blob_in,
107 &tmp_blob)) {
109 ntlmssp_end(&ntlmssp_state);
110 data_blob_free(&blob);
111 DEBUG(3,("Failed to parse challenges\n"));
112 return ADS_ERROR_NT(NT_STATUS_INVALID_PARAMETER);
114 data_blob_free(&tmp_blob);
115 } else if (rc == LDAP_SASL_BIND_IN_PROGRESS) {
116 if (!spnego_parse_auth_response(blob, nt_status, OID_NTLMSSP,
117 &blob_in)) {
119 ntlmssp_end(&ntlmssp_state);
120 data_blob_free(&blob);
121 DEBUG(3,("Failed to parse auth response\n"));
122 return ADS_ERROR_NT(NT_STATUS_INVALID_PARAMETER);
125 data_blob_free(&blob);
126 data_blob_free(&blob_out);
127 turn++;
128 } while (rc == LDAP_SASL_BIND_IN_PROGRESS && !NT_STATUS_IS_OK(nt_status));
130 /* we have a reference conter on ntlmssp_state, if we are signing
131 then the state will be kept by the signing engine */
133 ntlmssp_end(&ntlmssp_state);
135 return ADS_ERROR(rc);
138 #ifdef HAVE_KRB5
140 perform a LDAP/SASL/SPNEGO/KRB5 bind
142 static ADS_STATUS ads_sasl_spnego_krb5_bind(ADS_STRUCT *ads, const char *principal)
144 DATA_BLOB blob = data_blob_null;
145 struct berval cred, *scred = NULL;
146 DATA_BLOB session_key = data_blob_null;
147 int rc;
149 rc = spnego_gen_negTokenTarg(principal, ads->auth.time_offset, &blob, &session_key, 0,
150 &ads->auth.tgs_expire);
152 if (rc) {
153 return ADS_ERROR_KRB5(rc);
156 /* now send the auth packet and we should be done */
157 cred.bv_val = (char *)blob.data;
158 cred.bv_len = blob.length;
160 rc = ldap_sasl_bind_s(ads->ld, NULL, "GSS-SPNEGO", &cred, NULL, NULL, &scred);
162 data_blob_free(&blob);
163 data_blob_free(&session_key);
164 if(scred)
165 ber_bvfree(scred);
167 return ADS_ERROR(rc);
169 #endif
172 this performs a SASL/SPNEGO bind
174 static ADS_STATUS ads_sasl_spnego_bind(ADS_STRUCT *ads)
176 struct berval *scred=NULL;
177 int rc, i;
178 ADS_STATUS status;
179 DATA_BLOB blob;
180 char *principal = NULL;
181 char *OIDs[ASN1_MAX_OIDS];
182 #ifdef HAVE_KRB5
183 BOOL got_kerberos_mechanism = False;
184 #endif
186 rc = ldap_sasl_bind_s(ads->ld, NULL, "GSS-SPNEGO", NULL, NULL, NULL, &scred);
188 if (rc != LDAP_SASL_BIND_IN_PROGRESS) {
189 status = ADS_ERROR(rc);
190 goto failed;
193 blob = data_blob(scred->bv_val, scred->bv_len);
195 ber_bvfree(scred);
197 #if 0
198 file_save("sasl_spnego.dat", blob.data, blob.length);
199 #endif
201 /* the server sent us the first part of the SPNEGO exchange in the negprot
202 reply */
203 if (!spnego_parse_negTokenInit(blob, OIDs, &principal)) {
204 data_blob_free(&blob);
205 status = ADS_ERROR(LDAP_OPERATIONS_ERROR);
206 goto failed;
208 data_blob_free(&blob);
210 /* make sure the server understands kerberos */
211 for (i=0;OIDs[i];i++) {
212 DEBUG(3,("ads_sasl_spnego_bind: got OID=%s\n", OIDs[i]));
213 #ifdef HAVE_KRB5
214 if (strcmp(OIDs[i], OID_KERBEROS5_OLD) == 0 ||
215 strcmp(OIDs[i], OID_KERBEROS5) == 0) {
216 got_kerberos_mechanism = True;
218 #endif
219 free(OIDs[i]);
221 DEBUG(3,("ads_sasl_spnego_bind: got server principal name = %s\n", principal));
223 #ifdef HAVE_KRB5
224 if (!(ads->auth.flags & ADS_AUTH_DISABLE_KERBEROS) &&
225 got_kerberos_mechanism)
227 /* I've seen a child Windows 2000 domain not send
228 the principal name back in the first round of
229 the SASL bind reply. So we guess based on server
230 name and realm. --jerry */
231 if ( !principal ) {
232 if ( ads->server.realm && ads->server.ldap_server ) {
233 char *server, *server_realm;
235 server = SMB_STRDUP( ads->server.ldap_server );
236 server_realm = SMB_STRDUP( ads->server.realm );
238 if ( !server || !server_realm )
239 return ADS_ERROR(LDAP_NO_MEMORY);
241 strlower_m( server );
242 strupper_m( server_realm );
243 asprintf( &principal, "ldap/%s@%s", server, server_realm );
245 SAFE_FREE( server );
246 SAFE_FREE( server_realm );
248 if ( !principal )
249 return ADS_ERROR(LDAP_NO_MEMORY);
254 status = ads_sasl_spnego_krb5_bind(ads, principal);
255 if (ADS_ERR_OK(status)) {
256 SAFE_FREE(principal);
257 return status;
260 DEBUG(10,("ads_sasl_spnego_krb5_bind failed with: %s, "
261 "calling kinit\n", ads_errstr(status)));
263 status = ADS_ERROR_KRB5(ads_kinit_password(ads));
265 if (ADS_ERR_OK(status)) {
266 status = ads_sasl_spnego_krb5_bind(ads, principal);
269 /* only fallback to NTLMSSP if allowed */
270 if (ADS_ERR_OK(status) ||
271 !(ads->auth.flags & ADS_AUTH_ALLOW_NTLMSSP)) {
272 SAFE_FREE(principal);
273 return status;
276 #endif
278 SAFE_FREE(principal);
280 /* lets do NTLMSSP ... this has the big advantage that we don't need
281 to sync clocks, and we don't rely on special versions of the krb5
282 library for HMAC_MD4 encryption */
283 return ads_sasl_spnego_ntlmssp_bind(ads);
285 failed:
286 return status;
289 #ifdef HAVE_GSSAPI
290 #define MAX_GSS_PASSES 3
292 /* this performs a SASL/gssapi bind
293 we avoid using cyrus-sasl to make Samba more robust. cyrus-sasl
294 is very dependent on correctly configured DNS whereas
295 this routine is much less fragile
296 see RFC2078 and RFC2222 for details
298 static ADS_STATUS ads_sasl_gssapi_bind(ADS_STRUCT *ads)
300 uint32 minor_status;
301 gss_name_t serv_name;
302 gss_buffer_desc input_name;
303 gss_ctx_id_t context_handle = GSS_C_NO_CONTEXT;
304 gss_OID mech_type = GSS_C_NULL_OID;
305 gss_buffer_desc output_token, input_token;
306 uint32 ret_flags, conf_state;
307 struct berval cred;
308 struct berval *scred = NULL;
309 int i=0;
310 int gss_rc, rc;
311 uint8 *p;
312 uint32 max_msg_size = 0;
313 char *sname = NULL;
314 ADS_STATUS status;
315 krb5_principal principal = NULL;
316 krb5_context ctx = NULL;
317 krb5_enctype enc_types[] = {
318 #ifdef ENCTYPE_ARCFOUR_HMAC
319 ENCTYPE_ARCFOUR_HMAC,
320 #endif
321 ENCTYPE_DES_CBC_MD5,
322 ENCTYPE_NULL};
323 gss_OID_desc nt_principal =
324 {10, CONST_DISCARD(char *, "\052\206\110\206\367\022\001\002\002\002")};
326 /* we need to fetch a service ticket as the ldap user in the
327 servers realm, regardless of our realm */
328 asprintf(&sname, "ldap/%s@%s", ads->config.ldap_server_name, ads->config.realm);
330 initialize_krb5_error_table();
331 status = ADS_ERROR_KRB5(krb5_init_context(&ctx));
332 if (!ADS_ERR_OK(status)) {
333 SAFE_FREE(sname);
334 return status;
336 status = ADS_ERROR_KRB5(krb5_set_default_tgs_ktypes(ctx, enc_types));
337 if (!ADS_ERR_OK(status)) {
338 SAFE_FREE(sname);
339 krb5_free_context(ctx);
340 return status;
342 status = ADS_ERROR_KRB5(smb_krb5_parse_name(ctx, sname, &principal));
343 if (!ADS_ERR_OK(status)) {
344 SAFE_FREE(sname);
345 krb5_free_context(ctx);
346 return status;
349 input_name.value = &principal;
350 input_name.length = sizeof(principal);
352 gss_rc = gss_import_name(&minor_status, &input_name, &nt_principal, &serv_name);
355 * The MIT libraries have a *HORRIBLE* bug - input_value.value needs
356 * to point to the *address* of the krb5_principal, and the gss libraries
357 * to a shallow copy of the krb5_principal pointer - so we need to keep
358 * the krb5_principal around until we do the gss_release_name. MIT *SUCKS* !
359 * Just one more way in which MIT engineers screwed me over.... JRA.
362 SAFE_FREE(sname);
364 if (gss_rc) {
365 krb5_free_principal(ctx, principal);
366 krb5_free_context(ctx);
367 return ADS_ERROR_GSS(gss_rc, minor_status);
370 input_token.value = NULL;
371 input_token.length = 0;
373 for (i=0; i < MAX_GSS_PASSES; i++) {
374 gss_rc = gss_init_sec_context(&minor_status,
375 GSS_C_NO_CREDENTIAL,
376 &context_handle,
377 serv_name,
378 mech_type,
379 GSS_C_MUTUAL_FLAG | GSS_C_REPLAY_FLAG,
381 NULL,
382 &input_token,
383 NULL,
384 &output_token,
385 &ret_flags,
386 NULL);
388 if (input_token.value) {
389 gss_release_buffer(&minor_status, &input_token);
392 if (gss_rc && gss_rc != GSS_S_CONTINUE_NEEDED) {
393 status = ADS_ERROR_GSS(gss_rc, minor_status);
394 goto failed;
397 cred.bv_val = (char *)output_token.value;
398 cred.bv_len = output_token.length;
400 rc = ldap_sasl_bind_s(ads->ld, NULL, "GSSAPI", &cred, NULL, NULL,
401 &scred);
402 if (rc != LDAP_SASL_BIND_IN_PROGRESS) {
403 status = ADS_ERROR(rc);
404 goto failed;
407 if (output_token.value) {
408 gss_release_buffer(&minor_status, &output_token);
411 if (scred) {
412 input_token.value = scred->bv_val;
413 input_token.length = scred->bv_len;
414 } else {
415 input_token.value = NULL;
416 input_token.length = 0;
419 if (gss_rc == 0) break;
422 gss_rc = gss_unwrap(&minor_status,context_handle,&input_token,&output_token,
423 (int *)&conf_state,NULL);
424 if (gss_rc) {
425 status = ADS_ERROR_GSS(gss_rc, minor_status);
426 goto failed;
429 gss_release_buffer(&minor_status, &input_token);
431 p = (uint8 *)output_token.value;
433 #if 0
434 file_save("sasl_gssapi.dat", output_token.value, output_token.length);
435 #endif
437 if (p) {
438 max_msg_size = (p[1]<<16) | (p[2]<<8) | p[3];
441 gss_release_buffer(&minor_status, &output_token);
443 output_token.length = 4;
444 output_token.value = SMB_MALLOC(output_token.length);
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;
453 * we used to add sprintf("dn:%s", ads->config.bind_path) here.
454 * but using ads->config.bind_path is the wrong! It should be
455 * the DN of the user object!
457 * w2k3 gives an error when we send an incorrect DN, but sending nothing
458 * is ok and matches the information flow used in GSS-SPNEGO.
461 gss_rc = gss_wrap(&minor_status, context_handle,0,GSS_C_QOP_DEFAULT,
462 &output_token, (int *)&conf_state,
463 &input_token);
464 if (gss_rc) {
465 status = ADS_ERROR_GSS(gss_rc, minor_status);
466 goto failed;
469 free(output_token.value);
471 cred.bv_val = (char *)input_token.value;
472 cred.bv_len = input_token.length;
474 rc = ldap_sasl_bind_s(ads->ld, NULL, "GSSAPI", &cred, NULL, NULL,
475 &scred);
476 status = ADS_ERROR(rc);
478 gss_release_buffer(&minor_status, &input_token);
480 failed:
482 gss_release_name(&minor_status, &serv_name);
483 if (context_handle != GSS_C_NO_CONTEXT)
484 gss_delete_sec_context(&minor_status, &context_handle, GSS_C_NO_BUFFER);
485 krb5_free_principal(ctx, principal);
486 krb5_free_context(ctx);
488 if(scred)
489 ber_bvfree(scred);
490 return status;
492 #endif /* HAVE_GGSAPI */
494 /* mapping between SASL mechanisms and functions */
495 static struct {
496 const char *name;
497 ADS_STATUS (*fn)(ADS_STRUCT *);
498 } sasl_mechanisms[] = {
499 {"GSS-SPNEGO", ads_sasl_spnego_bind},
500 #ifdef HAVE_GSSAPI
501 {"GSSAPI", ads_sasl_gssapi_bind}, /* doesn't work with .NET RC1. No idea why */
502 #endif
503 {NULL, NULL}
506 ADS_STATUS ads_sasl_bind(ADS_STRUCT *ads)
508 const char *attrs[] = {"supportedSASLMechanisms", NULL};
509 char **values;
510 ADS_STATUS status;
511 int i, j;
512 LDAPMessage *res;
514 /* get a list of supported SASL mechanisms */
515 status = ads_do_search(ads, "", LDAP_SCOPE_BASE, "(objectclass=*)", attrs, &res);
516 if (!ADS_ERR_OK(status)) return status;
518 values = ldap_get_values(ads->ld, res, "supportedSASLMechanisms");
520 /* try our supported mechanisms in order */
521 for (i=0;sasl_mechanisms[i].name;i++) {
522 /* see if the server supports it */
523 for (j=0;values && values[j];j++) {
524 if (strcmp(values[j], sasl_mechanisms[i].name) == 0) {
525 DEBUG(4,("Found SASL mechanism %s\n", values[j]));
526 status = sasl_mechanisms[i].fn(ads);
527 ldap_value_free(values);
528 ldap_msgfree(res);
529 return status;
534 ldap_value_free(values);
535 ldap_msgfree(res);
536 return ADS_ERROR(LDAP_AUTH_METHOD_NOT_SUPPORTED);
539 #endif /* HAVE_LDAP */