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