r21240: Fix longstanding Bug #4009.
[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, 
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                 status = ads_sasl_spnego_krb5_bind(ads, principal);
228                 if (ADS_ERR_OK(status)) {
229                         SAFE_FREE(principal);
230                         return status;
231                 }
232
233                 DEBUG(10,("ads_sasl_spnego_krb5_bind failed with: %s, "
234                           "calling kinit\n", ads_errstr(status)));
235
236                 status = ADS_ERROR_KRB5(ads_kinit_password(ads)); 
237
238                 if (ADS_ERR_OK(status)) {
239                         status = ads_sasl_spnego_krb5_bind(ads, principal);
240                 }
241
242                 /* only fallback to NTLMSSP if allowed */
243                 if (ADS_ERR_OK(status) || 
244                     !(ads->auth.flags & ADS_AUTH_ALLOW_NTLMSSP)) {
245                         SAFE_FREE(principal);
246                         return status;
247                 }
248         }
249 #endif
250
251         SAFE_FREE(principal);
252
253         /* lets do NTLMSSP ... this has the big advantage that we don't need
254            to sync clocks, and we don't rely on special versions of the krb5 
255            library for HMAC_MD4 encryption */
256         return ads_sasl_spnego_ntlmssp_bind(ads);
257
258 failed:
259         return status;
260 }
261
262 #ifdef HAVE_GSSAPI
263 #define MAX_GSS_PASSES 3
264
265 /* this performs a SASL/gssapi bind
266    we avoid using cyrus-sasl to make Samba more robust. cyrus-sasl
267    is very dependent on correctly configured DNS whereas
268    this routine is much less fragile
269    see RFC2078 and RFC2222 for details
270 */
271 static ADS_STATUS ads_sasl_gssapi_bind(ADS_STRUCT *ads)
272 {
273         uint32 minor_status;
274         gss_name_t serv_name;
275         gss_buffer_desc input_name;
276         gss_ctx_id_t context_handle;
277         gss_OID mech_type = GSS_C_NULL_OID;
278         gss_buffer_desc output_token, input_token;
279         uint32 ret_flags, conf_state;
280         struct berval cred;
281         struct berval *scred = NULL;
282         int i=0;
283         int gss_rc, rc;
284         uint8 *p;
285         uint32 max_msg_size = 0;
286         char *sname;
287         ADS_STATUS status;
288         krb5_principal principal;
289         krb5_context ctx = NULL;
290         krb5_enctype enc_types[] = {
291 #ifdef ENCTYPE_ARCFOUR_HMAC
292                         ENCTYPE_ARCFOUR_HMAC,
293 #endif
294                         ENCTYPE_DES_CBC_MD5,
295                         ENCTYPE_NULL};
296         gss_OID_desc nt_principal = 
297         {10, CONST_DISCARD(char *, "\052\206\110\206\367\022\001\002\002\002")};
298
299         /* we need to fetch a service ticket as the ldap user in the
300            servers realm, regardless of our realm */
301         asprintf(&sname, "ldap/%s@%s", ads->config.ldap_server_name, ads->config.realm);
302
303         initialize_krb5_error_table();
304         status = ADS_ERROR_KRB5(krb5_init_context(&ctx));
305         if (!ADS_ERR_OK(status)) {
306                 return status;
307         }
308         status = ADS_ERROR_KRB5(krb5_set_default_tgs_ktypes(ctx, enc_types));
309         if (!ADS_ERR_OK(status)) {
310                 return status;
311         }
312         status = ADS_ERROR_KRB5(smb_krb5_parse_name(ctx, sname, &principal));
313         if (!ADS_ERR_OK(status)) {
314                 return status;
315         }
316
317         free(sname);
318         krb5_free_context(ctx); 
319
320         input_name.value = &principal;
321         input_name.length = sizeof(principal);
322
323         gss_rc = gss_import_name(&minor_status, &input_name, &nt_principal, &serv_name);
324         if (gss_rc) {
325                 return ADS_ERROR_GSS(gss_rc, minor_status);
326         }
327
328         context_handle = GSS_C_NO_CONTEXT;
329
330         input_token.value = NULL;
331         input_token.length = 0;
332
333         for (i=0; i < MAX_GSS_PASSES; i++) {
334                 gss_rc = gss_init_sec_context(&minor_status,
335                                           GSS_C_NO_CREDENTIAL,
336                                           &context_handle,
337                                           serv_name,
338                                           mech_type,
339                                           GSS_C_MUTUAL_FLAG | GSS_C_REPLAY_FLAG,
340                                           0,
341                                           NULL,
342                                           &input_token,
343                                           NULL,
344                                           &output_token,
345                                           &ret_flags,
346                                           NULL);
347
348                 if (input_token.value) {
349                         gss_release_buffer(&minor_status, &input_token);
350                 }
351
352                 if (gss_rc && gss_rc != GSS_S_CONTINUE_NEEDED) {
353                         status = ADS_ERROR_GSS(gss_rc, minor_status);
354                         goto failed;
355                 }
356
357                 cred.bv_val = (char *)output_token.value;
358                 cred.bv_len = output_token.length;
359
360                 rc = ldap_sasl_bind_s(ads->ld, NULL, "GSSAPI", &cred, NULL, NULL, 
361                                       &scred);
362                 if (rc != LDAP_SASL_BIND_IN_PROGRESS) {
363                         status = ADS_ERROR(rc);
364                         goto failed;
365                 }
366
367                 if (output_token.value) {
368                         gss_release_buffer(&minor_status, &output_token);
369                 }
370
371                 if (scred) {
372                         input_token.value = scred->bv_val;
373                         input_token.length = scred->bv_len;
374                 } else {
375                         input_token.value = NULL;
376                         input_token.length = 0;
377                 }
378
379                 if (gss_rc == 0) break;
380         }
381
382         gss_release_name(&minor_status, &serv_name);
383
384         gss_rc = gss_unwrap(&minor_status,context_handle,&input_token,&output_token,
385                             (int *)&conf_state,NULL);
386         if (gss_rc) {
387                 status = ADS_ERROR_GSS(gss_rc, minor_status);
388                 goto failed;
389         }
390
391         gss_release_buffer(&minor_status, &input_token);
392
393         p = (uint8 *)output_token.value;
394
395 #if 0
396         file_save("sasl_gssapi.dat", output_token.value, output_token.length);
397 #endif
398
399         if (p) {
400                 max_msg_size = (p[1]<<16) | (p[2]<<8) | p[3];
401         }
402
403         gss_release_buffer(&minor_status, &output_token);
404
405         output_token.value = SMB_MALLOC(strlen(ads->config.bind_path) + 8);
406         p = (uint8 *)output_token.value;
407
408         *p++ = 1; /* no sign & seal selection */
409         /* choose the same size as the server gave us */
410         *p++ = max_msg_size>>16;
411         *p++ = max_msg_size>>8;
412         *p++ = max_msg_size;
413         snprintf((char *)p, strlen(ads->config.bind_path)+4, "dn:%s", ads->config.bind_path);
414         p += strlen((const char *)p);
415
416         output_token.length = PTR_DIFF(p, output_token.value);
417
418         gss_rc = gss_wrap(&minor_status, context_handle,0,GSS_C_QOP_DEFAULT,
419                           &output_token, (int *)&conf_state,
420                           &input_token);
421         if (gss_rc) {
422                 status = ADS_ERROR_GSS(gss_rc, minor_status);
423                 goto failed;
424         }
425
426         free(output_token.value);
427
428         cred.bv_val = (char *)input_token.value;
429         cred.bv_len = input_token.length;
430
431         rc = ldap_sasl_bind_s(ads->ld, NULL, "GSSAPI", &cred, NULL, NULL, 
432                               &scred);
433         status = ADS_ERROR(rc);
434
435         gss_release_buffer(&minor_status, &input_token);
436
437 failed:
438         if(scred)
439                 ber_bvfree(scred);
440         return status;
441 }
442 #endif /* HAVE_GGSAPI */
443
444 /* mapping between SASL mechanisms and functions */
445 static struct {
446         const char *name;
447         ADS_STATUS (*fn)(ADS_STRUCT *);
448 } sasl_mechanisms[] = {
449         {"GSS-SPNEGO", ads_sasl_spnego_bind},
450 #ifdef HAVE_GSSAPI
451         {"GSSAPI", ads_sasl_gssapi_bind}, /* doesn't work with .NET RC1. No idea why */
452 #endif
453         {NULL, NULL}
454 };
455
456 ADS_STATUS ads_sasl_bind(ADS_STRUCT *ads)
457 {
458         const char *attrs[] = {"supportedSASLMechanisms", NULL};
459         char **values;
460         ADS_STATUS status;
461         int i, j;
462         LDAPMessage *res;
463
464         /* get a list of supported SASL mechanisms */
465         status = ads_do_search(ads, "", LDAP_SCOPE_BASE, "(objectclass=*)", attrs, &res);
466         if (!ADS_ERR_OK(status)) return status;
467
468         values = ldap_get_values(ads->ld, res, "supportedSASLMechanisms");
469
470         /* try our supported mechanisms in order */
471         for (i=0;sasl_mechanisms[i].name;i++) {
472                 /* see if the server supports it */
473                 for (j=0;values && values[j];j++) {
474                         if (strcmp(values[j], sasl_mechanisms[i].name) == 0) {
475                                 DEBUG(4,("Found SASL mechanism %s\n", values[j]));
476                                 status = sasl_mechanisms[i].fn(ads);
477                                 ldap_value_free(values);
478                                 ldap_msgfree(res);
479                                 return status;
480                         }
481                 }
482         }
483
484         ldap_value_free(values);
485         ldap_msgfree(res);
486         return ADS_ERROR(LDAP_AUTH_METHOD_NOT_SUPPORTED);
487 }
488
489 #endif /* HAVE_LDAP */
490