r21273: * Protect the sasl bind against a NULL principal string
[tprouty/samba.git] / source / 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, 
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;
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;
315         ADS_STATUS status;
316         krb5_principal principal;
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                 return status;
335         }
336         status = ADS_ERROR_KRB5(krb5_set_default_tgs_ktypes(ctx, enc_types));
337         if (!ADS_ERR_OK(status)) {
338                 return status;
339         }
340         status = ADS_ERROR_KRB5(smb_krb5_parse_name(ctx, sname, &principal));
341         if (!ADS_ERR_OK(status)) {
342                 return status;
343         }
344
345         free(sname);
346         krb5_free_context(ctx); 
347
348         input_name.value = &principal;
349         input_name.length = sizeof(principal);
350
351         gss_rc = gss_import_name(&minor_status, &input_name, &nt_principal, &serv_name);
352         if (gss_rc) {
353                 return ADS_ERROR_GSS(gss_rc, minor_status);
354         }
355
356         context_handle = GSS_C_NO_CONTEXT;
357
358         input_token.value = NULL;
359         input_token.length = 0;
360
361         for (i=0; i < MAX_GSS_PASSES; i++) {
362                 gss_rc = gss_init_sec_context(&minor_status,
363                                           GSS_C_NO_CREDENTIAL,
364                                           &context_handle,
365                                           serv_name,
366                                           mech_type,
367                                           GSS_C_MUTUAL_FLAG | GSS_C_REPLAY_FLAG,
368                                           0,
369                                           NULL,
370                                           &input_token,
371                                           NULL,
372                                           &output_token,
373                                           &ret_flags,
374                                           NULL);
375
376                 if (input_token.value) {
377                         gss_release_buffer(&minor_status, &input_token);
378                 }
379
380                 if (gss_rc && gss_rc != GSS_S_CONTINUE_NEEDED) {
381                         status = ADS_ERROR_GSS(gss_rc, minor_status);
382                         goto failed;
383                 }
384
385                 cred.bv_val = (char *)output_token.value;
386                 cred.bv_len = output_token.length;
387
388                 rc = ldap_sasl_bind_s(ads->ld, NULL, "GSSAPI", &cred, NULL, NULL, 
389                                       &scred);
390                 if (rc != LDAP_SASL_BIND_IN_PROGRESS) {
391                         status = ADS_ERROR(rc);
392                         goto failed;
393                 }
394
395                 if (output_token.value) {
396                         gss_release_buffer(&minor_status, &output_token);
397                 }
398
399                 if (scred) {
400                         input_token.value = scred->bv_val;
401                         input_token.length = scred->bv_len;
402                 } else {
403                         input_token.value = NULL;
404                         input_token.length = 0;
405                 }
406
407                 if (gss_rc == 0) break;
408         }
409
410         gss_release_name(&minor_status, &serv_name);
411
412         gss_rc = gss_unwrap(&minor_status,context_handle,&input_token,&output_token,
413                             (int *)&conf_state,NULL);
414         if (gss_rc) {
415                 status = ADS_ERROR_GSS(gss_rc, minor_status);
416                 goto failed;
417         }
418
419         gss_release_buffer(&minor_status, &input_token);
420
421         p = (uint8 *)output_token.value;
422
423 #if 0
424         file_save("sasl_gssapi.dat", output_token.value, output_token.length);
425 #endif
426
427         if (p) {
428                 max_msg_size = (p[1]<<16) | (p[2]<<8) | p[3];
429         }
430
431         gss_release_buffer(&minor_status, &output_token);
432
433         output_token.value = SMB_MALLOC(strlen(ads->config.bind_path) + 8);
434         p = (uint8 *)output_token.value;
435
436         *p++ = 1; /* no sign & seal selection */
437         /* choose the same size as the server gave us */
438         *p++ = max_msg_size>>16;
439         *p++ = max_msg_size>>8;
440         *p++ = max_msg_size;
441         snprintf((char *)p, strlen(ads->config.bind_path)+4, "dn:%s", ads->config.bind_path);
442         p += strlen((const char *)p);
443
444         output_token.length = PTR_DIFF(p, output_token.value);
445
446         gss_rc = gss_wrap(&minor_status, context_handle,0,GSS_C_QOP_DEFAULT,
447                           &output_token, (int *)&conf_state,
448                           &input_token);
449         if (gss_rc) {
450                 status = ADS_ERROR_GSS(gss_rc, minor_status);
451                 goto failed;
452         }
453
454         free(output_token.value);
455
456         cred.bv_val = (char *)input_token.value;
457         cred.bv_len = input_token.length;
458
459         rc = ldap_sasl_bind_s(ads->ld, NULL, "GSSAPI", &cred, NULL, NULL, 
460                               &scred);
461         status = ADS_ERROR(rc);
462
463         gss_release_buffer(&minor_status, &input_token);
464
465 failed:
466         if(scred)
467                 ber_bvfree(scred);
468         return status;
469 }
470 #endif /* HAVE_GGSAPI */
471
472 /* mapping between SASL mechanisms and functions */
473 static struct {
474         const char *name;
475         ADS_STATUS (*fn)(ADS_STRUCT *);
476 } sasl_mechanisms[] = {
477         {"GSS-SPNEGO", ads_sasl_spnego_bind},
478 #ifdef HAVE_GSSAPI
479         {"GSSAPI", ads_sasl_gssapi_bind}, /* doesn't work with .NET RC1. No idea why */
480 #endif
481         {NULL, NULL}
482 };
483
484 ADS_STATUS ads_sasl_bind(ADS_STRUCT *ads)
485 {
486         const char *attrs[] = {"supportedSASLMechanisms", NULL};
487         char **values;
488         ADS_STATUS status;
489         int i, j;
490         LDAPMessage *res;
491
492         /* get a list of supported SASL mechanisms */
493         status = ads_do_search(ads, "", LDAP_SCOPE_BASE, "(objectclass=*)", attrs, &res);
494         if (!ADS_ERR_OK(status)) return status;
495
496         values = ldap_get_values(ads->ld, res, "supportedSASLMechanisms");
497
498         /* try our supported mechanisms in order */
499         for (i=0;sasl_mechanisms[i].name;i++) {
500                 /* see if the server supports it */
501                 for (j=0;values && values[j];j++) {
502                         if (strcmp(values[j], sasl_mechanisms[i].name) == 0) {
503                                 DEBUG(4,("Found SASL mechanism %s\n", values[j]));
504                                 status = sasl_mechanisms[i].fn(ads);
505                                 ldap_value_free(values);
506                                 ldap_msgfree(res);
507                                 return status;
508                         }
509                 }
510         }
511
512         ldap_value_free(values);
513         ldap_msgfree(res);
514         return ADS_ERROR(LDAP_AUTH_METHOD_NOT_SUPPORTED);
515 }
516
517 #endif /* HAVE_LDAP */
518