r20403: Cleaning out my Samba 3.0 tree:
[abartlet/samba.git/.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
46         if (!NT_STATUS_IS_OK(nt_status = ntlmssp_set_username(ntlmssp_state, ads->auth.user_name))) {
47                 return ADS_ERROR_NT(nt_status);
48         }
49         if (!NT_STATUS_IS_OK(nt_status = ntlmssp_set_domain(ntlmssp_state, ads->auth.realm))) {
50                 return ADS_ERROR_NT(nt_status);
51         }
52         if (!NT_STATUS_IS_OK(nt_status = ntlmssp_set_password(ntlmssp_state, ads->auth.password))) {
53                 return ADS_ERROR_NT(nt_status);
54         }
55
56         blob_in = data_blob(NULL, 0);
57
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);
71                         }
72
73                         data_blob_free(&blob_out);
74
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);
83                                 }
84
85                                 ntlmssp_end(&ntlmssp_state);
86                                 return ADS_ERROR(rc);
87                         }
88                         if (scred) {
89                                 blob = data_blob(scred->bv_val, scred->bv_len);
90                                 ber_bvfree(scred);
91                         } else {
92                                 blob = data_blob(NULL, 0);
93                         }
94
95                 } else {
96
97                         ntlmssp_end(&ntlmssp_state);
98                         data_blob_free(&blob_out);
99                         return ADS_ERROR_NT(nt_status);
100                 }
101                 
102                 if ((turn == 1) && 
103                     (rc == LDAP_SASL_BIND_IN_PROGRESS)) {
104                         DATA_BLOB tmp_blob = data_blob(NULL, 0);
105                         /* the server might give us back two challenges */
106                         if (!spnego_parse_challenge(blob, &blob_in, 
107                                                     &tmp_blob)) {
108
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);
113                         }
114                         data_blob_free(&tmp_blob);
115                 } else if (rc == LDAP_SASL_BIND_IN_PROGRESS) {
116                         if (!spnego_parse_auth_response(blob, nt_status, 
117                                                         &blob_in)) {
118
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);
123                         }
124                 }
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));
129         
130         /* we have a reference conter on ntlmssp_state, if we are signing
131            then the state will be kept by the signing engine */
132
133         ntlmssp_end(&ntlmssp_state);
134
135         return ADS_ERROR(rc);
136 }
137
138 #ifdef HAVE_KRB5
139 /* 
140    perform a LDAP/SASL/SPNEGO/KRB5 bind
141 */
142 static ADS_STATUS ads_sasl_spnego_krb5_bind(ADS_STRUCT *ads, const char *principal)
143 {
144         DATA_BLOB blob = data_blob(NULL, 0);
145         struct berval cred, *scred = NULL;
146         DATA_BLOB session_key = data_blob(NULL, 0);
147         int rc;
148
149         rc = spnego_gen_negTokenTarg(principal, ads->auth.time_offset, &blob, &session_key, 0);
150
151         if (rc) {
152                 return ADS_ERROR_KRB5(rc);
153         }
154
155         /* now send the auth packet and we should be done */
156         cred.bv_val = (char *)blob.data;
157         cred.bv_len = blob.length;
158
159         rc = ldap_sasl_bind_s(ads->ld, NULL, "GSS-SPNEGO", &cred, NULL, NULL, &scred);
160
161         data_blob_free(&blob);
162         data_blob_free(&session_key);
163         if(scred)
164                 ber_bvfree(scred);
165
166         return ADS_ERROR(rc);
167 }
168 #endif
169
170 /* 
171    this performs a SASL/SPNEGO bind
172 */
173 static ADS_STATUS ads_sasl_spnego_bind(ADS_STRUCT *ads)
174 {
175         struct berval *scred=NULL;
176         int rc, i;
177         ADS_STATUS status;
178         DATA_BLOB blob;
179         char *principal = NULL;
180         char *OIDs[ASN1_MAX_OIDS];
181 #ifdef HAVE_KRB5
182         BOOL got_kerberos_mechanism = False;
183 #endif
184
185         rc = ldap_sasl_bind_s(ads->ld, NULL, "GSS-SPNEGO", NULL, NULL, NULL, &scred);
186
187         if (rc != LDAP_SASL_BIND_IN_PROGRESS) {
188                 status = ADS_ERROR(rc);
189                 goto failed;
190         }
191
192         blob = data_blob(scred->bv_val, scred->bv_len);
193
194         ber_bvfree(scred);
195
196 #if 0
197         file_save("sasl_spnego.dat", blob.data, blob.length);
198 #endif
199
200         /* the server sent us the first part of the SPNEGO exchange in the negprot 
201            reply */
202         if (!spnego_parse_negTokenInit(blob, OIDs, &principal)) {
203                 data_blob_free(&blob);
204                 status = ADS_ERROR(LDAP_OPERATIONS_ERROR);
205                 goto failed;
206         }
207         data_blob_free(&blob);
208
209         /* make sure the server understands kerberos */
210         for (i=0;OIDs[i];i++) {
211                 DEBUG(3,("ads_sasl_spnego_bind: got OID=%s\n", OIDs[i]));
212 #ifdef HAVE_KRB5
213                 if (strcmp(OIDs[i], OID_KERBEROS5_OLD) == 0 ||
214                     strcmp(OIDs[i], OID_KERBEROS5) == 0) {
215                         got_kerberos_mechanism = True;
216                 }
217 #endif
218                 free(OIDs[i]);
219         }
220         DEBUG(3,("ads_sasl_spnego_bind: got server principal name =%s\n", principal));
221
222 #ifdef HAVE_KRB5
223         if (!(ads->auth.flags & ADS_AUTH_DISABLE_KERBEROS) &&
224             got_kerberos_mechanism) {
225                 status = ads_sasl_spnego_krb5_bind(ads, principal);
226                 if (ADS_ERR_OK(status)) {
227                         SAFE_FREE(principal);
228                         return status;
229                 }
230
231                 status = ADS_ERROR_KRB5(ads_kinit_password(ads)); 
232
233                 if (ADS_ERR_OK(status)) {
234                         status = ads_sasl_spnego_krb5_bind(ads, principal);
235                 }
236
237                 /* only fallback to NTLMSSP if allowed */
238                 if (ADS_ERR_OK(status) || 
239                     !(ads->auth.flags & ADS_AUTH_ALLOW_NTLMSSP)) {
240                         SAFE_FREE(principal);
241                         return status;
242                 }
243         }
244 #endif
245
246         SAFE_FREE(principal);
247
248         /* lets do NTLMSSP ... this has the big advantage that we don't need
249            to sync clocks, and we don't rely on special versions of the krb5 
250            library for HMAC_MD4 encryption */
251         return ads_sasl_spnego_ntlmssp_bind(ads);
252
253 failed:
254         return status;
255 }
256
257 #ifdef HAVE_GSSAPI
258 #define MAX_GSS_PASSES 3
259
260 /* this performs a SASL/gssapi bind
261    we avoid using cyrus-sasl to make Samba more robust. cyrus-sasl
262    is very dependent on correctly configured DNS whereas
263    this routine is much less fragile
264    see RFC2078 and RFC2222 for details
265 */
266 static ADS_STATUS ads_sasl_gssapi_bind(ADS_STRUCT *ads)
267 {
268         uint32 minor_status;
269         gss_name_t serv_name;
270         gss_buffer_desc input_name;
271         gss_ctx_id_t context_handle;
272         gss_OID mech_type = GSS_C_NULL_OID;
273         gss_buffer_desc output_token, input_token;
274         uint32 ret_flags, conf_state;
275         struct berval cred;
276         struct berval *scred = NULL;
277         int i=0;
278         int gss_rc, rc;
279         uint8 *p;
280         uint32 max_msg_size = 0;
281         char *sname;
282         ADS_STATUS status;
283         krb5_principal principal;
284         krb5_context ctx = NULL;
285         gss_OID_desc nt_principal = 
286         {10, CONST_DISCARD(char *, "\052\206\110\206\367\022\001\002\002\002")};
287
288         /* we need to fetch a service ticket as the ldap user in the
289            servers realm, regardless of our realm */
290         asprintf(&sname, "ldap/%s@%s", ads->config.ldap_server_name, ads->config.realm);
291
292         initialize_krb5_error_table();
293         status = ADS_ERROR_KRB5(krb5_init_context(&ctx));
294         if (!ADS_ERR_OK(status)) {
295                 return status;
296         }
297         status = ADS_ERROR_KRB5(smb_krb5_parse_name(ctx, sname, &principal));
298         if (!ADS_ERR_OK(status)) {
299                 return status;
300         }
301
302         free(sname);
303         krb5_free_context(ctx); 
304
305         input_name.value = &principal;
306         input_name.length = sizeof(principal);
307
308         gss_rc = gss_import_name(&minor_status, &input_name, &nt_principal, &serv_name);
309         if (gss_rc) {
310                 return ADS_ERROR_GSS(gss_rc, minor_status);
311         }
312
313         context_handle = GSS_C_NO_CONTEXT;
314
315         input_token.value = NULL;
316         input_token.length = 0;
317
318         for (i=0; i < MAX_GSS_PASSES; i++) {
319                 gss_rc = gss_init_sec_context(&minor_status,
320                                           GSS_C_NO_CREDENTIAL,
321                                           &context_handle,
322                                           serv_name,
323                                           mech_type,
324                                           GSS_C_MUTUAL_FLAG | GSS_C_REPLAY_FLAG,
325                                           0,
326                                           NULL,
327                                           &input_token,
328                                           NULL,
329                                           &output_token,
330                                           &ret_flags,
331                                           NULL);
332
333                 if (input_token.value) {
334                         gss_release_buffer(&minor_status, &input_token);
335                 }
336
337                 if (gss_rc && gss_rc != GSS_S_CONTINUE_NEEDED) {
338                         status = ADS_ERROR_GSS(gss_rc, minor_status);
339                         goto failed;
340                 }
341
342                 cred.bv_val = (char *)output_token.value;
343                 cred.bv_len = output_token.length;
344
345                 rc = ldap_sasl_bind_s(ads->ld, NULL, "GSSAPI", &cred, NULL, NULL, 
346                                       &scred);
347                 if (rc != LDAP_SASL_BIND_IN_PROGRESS) {
348                         status = ADS_ERROR(rc);
349                         goto failed;
350                 }
351
352                 if (output_token.value) {
353                         gss_release_buffer(&minor_status, &output_token);
354                 }
355
356                 if (scred) {
357                         input_token.value = scred->bv_val;
358                         input_token.length = scred->bv_len;
359                 } else {
360                         input_token.value = NULL;
361                         input_token.length = 0;
362                 }
363
364                 if (gss_rc == 0) break;
365         }
366
367         gss_release_name(&minor_status, &serv_name);
368
369         gss_rc = gss_unwrap(&minor_status,context_handle,&input_token,&output_token,
370                             (int *)&conf_state,NULL);
371         if (gss_rc) {
372                 status = ADS_ERROR_GSS(gss_rc, minor_status);
373                 goto failed;
374         }
375
376         gss_release_buffer(&minor_status, &input_token);
377
378         p = (uint8 *)output_token.value;
379
380 #if 0
381         file_save("sasl_gssapi.dat", output_token.value, output_token.length);
382 #endif
383
384         if (p) {
385                 max_msg_size = (p[1]<<16) | (p[2]<<8) | p[3];
386         }
387
388         gss_release_buffer(&minor_status, &output_token);
389
390         output_token.value = SMB_MALLOC(strlen(ads->config.bind_path) + 8);
391         p = (uint8 *)output_token.value;
392
393         *p++ = 1; /* no sign & seal selection */
394         /* choose the same size as the server gave us */
395         *p++ = max_msg_size>>16;
396         *p++ = max_msg_size>>8;
397         *p++ = max_msg_size;
398         snprintf((char *)p, strlen(ads->config.bind_path)+4, "dn:%s", ads->config.bind_path);
399         p += strlen((const char *)p);
400
401         output_token.length = PTR_DIFF(p, output_token.value);
402
403         gss_rc = gss_wrap(&minor_status, context_handle,0,GSS_C_QOP_DEFAULT,
404                           &output_token, (int *)&conf_state,
405                           &input_token);
406         if (gss_rc) {
407                 status = ADS_ERROR_GSS(gss_rc, minor_status);
408                 goto failed;
409         }
410
411         free(output_token.value);
412
413         cred.bv_val = (char *)input_token.value;
414         cred.bv_len = input_token.length;
415
416         rc = ldap_sasl_bind_s(ads->ld, NULL, "GSSAPI", &cred, NULL, NULL, 
417                               &scred);
418         status = ADS_ERROR(rc);
419
420         gss_release_buffer(&minor_status, &input_token);
421
422 failed:
423         if(scred)
424                 ber_bvfree(scred);
425         return status;
426 }
427 #endif /* HAVE_GGSAPI */
428
429 /* mapping between SASL mechanisms and functions */
430 static struct {
431         const char *name;
432         ADS_STATUS (*fn)(ADS_STRUCT *);
433 } sasl_mechanisms[] = {
434         {"GSS-SPNEGO", ads_sasl_spnego_bind},
435 #ifdef HAVE_GSSAPI
436         {"GSSAPI", ads_sasl_gssapi_bind}, /* doesn't work with .NET RC1. No idea why */
437 #endif
438         {NULL, NULL}
439 };
440
441 ADS_STATUS ads_sasl_bind(ADS_STRUCT *ads)
442 {
443         const char *attrs[] = {"supportedSASLMechanisms", NULL};
444         char **values;
445         ADS_STATUS status;
446         int i, j;
447         LDAPMessage *res;
448
449         /* get a list of supported SASL mechanisms */
450         status = ads_do_search(ads, "", LDAP_SCOPE_BASE, "(objectclass=*)", attrs, &res);
451         if (!ADS_ERR_OK(status)) return status;
452
453         values = ldap_get_values(ads->ld, res, "supportedSASLMechanisms");
454
455         /* try our supported mechanisms in order */
456         for (i=0;sasl_mechanisms[i].name;i++) {
457                 /* see if the server supports it */
458                 for (j=0;values && values[j];j++) {
459                         if (strcmp(values[j], sasl_mechanisms[i].name) == 0) {
460                                 DEBUG(4,("Found SASL mechanism %s\n", values[j]));
461                                 status = sasl_mechanisms[i].fn(ads);
462                                 ldap_value_free(values);
463                                 ldap_msgfree(res);
464                                 return status;
465                         }
466                 }
467         }
468
469         ldap_value_free(values);
470         ldap_msgfree(res);
471         return ADS_ERROR(LDAP_AUTH_METHOD_NOT_SUPPORTED);
472 }
473
474 #endif /* HAVE_LDAP */
475