r22153: fix LDAP SASL "GSSAPI" bind against w2k3, this isn't critical
[sfrench/samba-autobuild/.git] / source3 / libads / sasl.c
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 */
29 static 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, OID_NTLMSSP, 
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
140 /* 
141    perform a LDAP/SASL/SPNEGO/KRB5 bind
142 */
143 static ADS_STATUS ads_sasl_spnego_krb5_bind(ADS_STRUCT *ads, const char *principal)
144 {
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;
149
150         rc = spnego_gen_negTokenTarg(principal, ads->auth.time_offset, &blob, &session_key, 0,
151                                      &ads->auth.tgs_expire);
152
153         if (rc) {
154                 return ADS_ERROR_KRB5(rc);
155         }
156
157         /* now send the auth packet and we should be done */
158         cred.bv_val = (char *)blob.data;
159         cred.bv_len = blob.length;
160
161         rc = ldap_sasl_bind_s(ads->ld, NULL, "GSS-SPNEGO", &cred, NULL, NULL, &scred);
162
163         data_blob_free(&blob);
164         data_blob_free(&session_key);
165         if(scred)
166                 ber_bvfree(scred);
167
168         return ADS_ERROR(rc);
169 }
170 #endif
171
172 /* 
173    this performs a SASL/SPNEGO bind
174 */
175 static ADS_STATUS ads_sasl_spnego_bind(ADS_STRUCT *ads)
176 {
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
186
187         rc = ldap_sasl_bind_s(ads->ld, NULL, "GSS-SPNEGO", NULL, NULL, NULL, &scred);
188
189         if (rc != LDAP_SASL_BIND_IN_PROGRESS) {
190                 status = ADS_ERROR(rc);
191                 goto failed;
192         }
193
194         blob = data_blob(scred->bv_val, scred->bv_len);
195
196         ber_bvfree(scred);
197
198 #if 0
199         file_save("sasl_spnego.dat", blob.data, blob.length);
200 #endif
201
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;
208         }
209         data_blob_free(&blob);
210
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;
218                 }
219 #endif
220                 free(OIDs[i]);
221         }
222         DEBUG(3,("ads_sasl_spnego_bind: got server principal name = %s\n", principal));
223
224 #ifdef HAVE_KRB5
225         if (!(ads->auth.flags & ADS_AUTH_DISABLE_KERBEROS) &&
226             got_kerberos_mechanism) 
227         {
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;
235                                 
236                                 server = SMB_STRDUP( ads->server.ldap_server );
237                                 server_realm = SMB_STRDUP( ads->server.realm );
238                                 
239                                 if ( !server || !server_realm )
240                                         return ADS_ERROR(LDAP_NO_MEMORY);
241
242                                 strlower_m( server );
243                                 strupper_m( server_realm );                             
244                                 asprintf( &principal, "ldap/%s@%s", server, server_realm );
245
246                                 SAFE_FREE( server );
247                                 SAFE_FREE( server_realm );
248
249                                 if ( !principal )
250                                         return ADS_ERROR(LDAP_NO_MEMORY);                               
251                         }
252                         
253                 }
254                 
255                 status = ads_sasl_spnego_krb5_bind(ads, principal);
256                 if (ADS_ERR_OK(status)) {
257                         SAFE_FREE(principal);
258                         return status;
259                 }
260
261                 DEBUG(10,("ads_sasl_spnego_krb5_bind failed with: %s, "
262                           "calling kinit\n", ads_errstr(status)));
263
264                 status = ADS_ERROR_KRB5(ads_kinit_password(ads)); 
265
266                 if (ADS_ERR_OK(status)) {
267                         status = ads_sasl_spnego_krb5_bind(ads, principal);
268                 }
269
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;
275                 }
276         }
277 #endif
278
279         SAFE_FREE(principal);
280
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);
285
286 failed:
287         return status;
288 }
289
290 #ifdef HAVE_GSSAPI
291 #define MAX_GSS_PASSES 3
292
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
298 */
299 static ADS_STATUS ads_sasl_gssapi_bind(ADS_STRUCT *ads)
300 {
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")};
326
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);
330
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;
336         }
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;
342         }
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;
348         }
349
350         input_name.value = &principal;
351         input_name.length = sizeof(principal);
352
353         gss_rc = gss_import_name(&minor_status, &input_name, &nt_principal, &serv_name);
354
355         /*
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.
361          */
362
363         SAFE_FREE(sname);
364
365         if (gss_rc) {
366                 krb5_free_principal(ctx, principal);
367                 krb5_free_context(ctx); 
368                 return ADS_ERROR_GSS(gss_rc, minor_status);
369         }
370
371         input_token.value = NULL;
372         input_token.length = 0;
373
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,
381                                           0,
382                                           NULL,
383                                           &input_token,
384                                           NULL,
385                                           &output_token,
386                                           &ret_flags,
387                                           NULL);
388
389                 if (input_token.value) {
390                         gss_release_buffer(&minor_status, &input_token);
391                 }
392
393                 if (gss_rc && gss_rc != GSS_S_CONTINUE_NEEDED) {
394                         status = ADS_ERROR_GSS(gss_rc, minor_status);
395                         goto failed;
396                 }
397
398                 cred.bv_val = (char *)output_token.value;
399                 cred.bv_len = output_token.length;
400
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;
406                 }
407
408                 if (output_token.value) {
409                         gss_release_buffer(&minor_status, &output_token);
410                 }
411
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;
418                 }
419
420                 if (gss_rc == 0) break;
421         }
422
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;
428         }
429
430         gss_release_buffer(&minor_status, &input_token);
431
432         p = (uint8 *)output_token.value;
433
434 #if 0
435         file_save("sasl_gssapi.dat", output_token.value, output_token.length);
436 #endif
437
438         if (p) {
439                 max_msg_size = (p[1]<<16) | (p[2]<<8) | p[3];
440         }
441
442         gss_release_buffer(&minor_status, &output_token);
443
444         output_token.length = 4;
445         output_token.value = SMB_MALLOC(output_token.length);
446         p = (uint8 *)output_token.value;
447
448         *p++ = 1; /* no sign & seal selection */
449         /* choose the same size as the server gave us */
450         *p++ = max_msg_size>>16;
451         *p++ = max_msg_size>>8;
452         *p++ = max_msg_size;
453         /*
454          * we used to add sprintf("dn:%s", ads->config.bind_path) here.
455          * but using ads->config.bind_path is the wrong! It should be
456          * the DN of the user object!
457          *
458          * w2k3 gives an error when we send an incorrect DN, but sending nothing
459          * is ok and matches the information flow used in GSS-SPNEGO.
460          */
461
462         gss_rc = gss_wrap(&minor_status, context_handle,0,GSS_C_QOP_DEFAULT,
463                           &output_token, (int *)&conf_state,
464                           &input_token);
465         if (gss_rc) {
466                 status = ADS_ERROR_GSS(gss_rc, minor_status);
467                 goto failed;
468         }
469
470         free(output_token.value);
471
472         cred.bv_val = (char *)input_token.value;
473         cred.bv_len = input_token.length;
474
475         rc = ldap_sasl_bind_s(ads->ld, NULL, "GSSAPI", &cred, NULL, NULL, 
476                               &scred);
477         status = ADS_ERROR(rc);
478
479         gss_release_buffer(&minor_status, &input_token);
480
481 failed:
482
483         gss_release_name(&minor_status, &serv_name);
484         if (context_handle != GSS_C_NO_CONTEXT)
485                 gss_delete_sec_context(&minor_status, &context_handle, GSS_C_NO_BUFFER);
486         krb5_free_principal(ctx, principal);
487         krb5_free_context(ctx); 
488
489         if(scred)
490                 ber_bvfree(scred);
491         return status;
492 }
493 #endif /* HAVE_GGSAPI */
494
495 /* mapping between SASL mechanisms and functions */
496 static struct {
497         const char *name;
498         ADS_STATUS (*fn)(ADS_STRUCT *);
499 } sasl_mechanisms[] = {
500         {"GSS-SPNEGO", ads_sasl_spnego_bind},
501 #ifdef HAVE_GSSAPI
502         {"GSSAPI", ads_sasl_gssapi_bind}, /* doesn't work with .NET RC1. No idea why */
503 #endif
504         {NULL, NULL}
505 };
506
507 ADS_STATUS ads_sasl_bind(ADS_STRUCT *ads)
508 {
509         const char *attrs[] = {"supportedSASLMechanisms", NULL};
510         char **values;
511         ADS_STATUS status;
512         int i, j;
513         LDAPMessage *res;
514
515         /* get a list of supported SASL mechanisms */
516         status = ads_do_search(ads, "", LDAP_SCOPE_BASE, "(objectclass=*)", attrs, &res);
517         if (!ADS_ERR_OK(status)) return status;
518
519         values = ldap_get_values(ads->ld, res, "supportedSASLMechanisms");
520
521         /* try our supported mechanisms in order */
522         for (i=0;sasl_mechanisms[i].name;i++) {
523                 /* see if the server supports it */
524                 for (j=0;values && values[j];j++) {
525                         if (strcmp(values[j], sasl_mechanisms[i].name) == 0) {
526                                 DEBUG(4,("Found SASL mechanism %s\n", values[j]));
527                                 status = sasl_mechanisms[i].fn(ads);
528                                 ldap_value_free(values);
529                                 ldap_msgfree(res);
530                                 return status;
531                         }
532                 }
533         }
534
535         ldap_value_free(values);
536         ldap_msgfree(res);
537         return ADS_ERROR(LDAP_AUTH_METHOD_NOT_SUPPORTED);
538 }
539
540 #endif /* HAVE_LDAP */
541