source: trunk/samba/source/libads/sasl.c @ 26

Last change on this file since 26 was 26, checked in by Paul Smedley, 14 years ago

Updated source to 3.0.25rc1

File size: 14.4 KB
Line 
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*/
29static 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*/
143static 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*/
175static 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
286failed:
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*/
299static 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.value = SMB_MALLOC(strlen(ads->config.bind_path) + 8);
445        p = (uint8 *)output_token.value;
446
447        *p++ = 1; /* no sign & seal selection */
448        /* choose the same size as the server gave us */
449        *p++ = max_msg_size>>16;
450        *p++ = max_msg_size>>8;
451        *p++ = max_msg_size;
452        snprintf((char *)p, strlen(ads->config.bind_path)+4, "dn:%s", ads->config.bind_path);
453        p += strlen((const char *)p);
454
455        output_token.length = PTR_DIFF(p, output_token.value);
456
457        gss_rc = gss_wrap(&minor_status, context_handle,0,GSS_C_QOP_DEFAULT,
458                          &output_token, (int *)&conf_state,
459                          &input_token);
460        if (gss_rc) {
461                status = ADS_ERROR_GSS(gss_rc, minor_status);
462                goto failed;
463        }
464
465        free(output_token.value);
466
467        cred.bv_val = (char *)input_token.value;
468        cred.bv_len = input_token.length;
469
470        rc = ldap_sasl_bind_s(ads->ld, NULL, "GSSAPI", &cred, NULL, NULL, 
471                              &scred);
472        status = ADS_ERROR(rc);
473
474        gss_release_buffer(&minor_status, &input_token);
475
476failed:
477
478        gss_release_name(&minor_status, &serv_name);
479        if (context_handle != GSS_C_NO_CONTEXT)
480                gss_delete_sec_context(&minor_status, &context_handle, GSS_C_NO_BUFFER);
481        krb5_free_principal(ctx, principal);
482        krb5_free_context(ctx); 
483
484        if(scred)
485                ber_bvfree(scred);
486        return status;
487}
488#endif /* HAVE_GGSAPI */
489
490/* mapping between SASL mechanisms and functions */
491static struct {
492        const char *name;
493        ADS_STATUS (*fn)(ADS_STRUCT *);
494} sasl_mechanisms[] = {
495        {"GSS-SPNEGO", ads_sasl_spnego_bind},
496#ifdef HAVE_GSSAPI
497        {"GSSAPI", ads_sasl_gssapi_bind}, /* doesn't work with .NET RC1. No idea why */
498#endif
499        {NULL, NULL}
500};
501
502ADS_STATUS ads_sasl_bind(ADS_STRUCT *ads)
503{
504        const char *attrs[] = {"supportedSASLMechanisms", NULL};
505        char **values;
506        ADS_STATUS status;
507        int i, j;
508        LDAPMessage *res;
509
510        /* get a list of supported SASL mechanisms */
511        status = ads_do_search(ads, "", LDAP_SCOPE_BASE, "(objectclass=*)", attrs, &res);
512        if (!ADS_ERR_OK(status)) return status;
513
514        values = ldap_get_values(ads->ld, res, "supportedSASLMechanisms");
515
516        /* try our supported mechanisms in order */
517        for (i=0;sasl_mechanisms[i].name;i++) {
518                /* see if the server supports it */
519                for (j=0;values && values[j];j++) {
520                        if (strcmp(values[j], sasl_mechanisms[i].name) == 0) {
521                                DEBUG(4,("Found SASL mechanism %s\n", values[j]));
522                                status = sasl_mechanisms[i].fn(ads);
523                                ldap_value_free(values);
524                                ldap_msgfree(res);
525                                return status;
526                        }
527                }
528        }
529
530        ldap_value_free(values);
531        ldap_msgfree(res);
532        return ADS_ERROR(LDAP_AUTH_METHOD_NOT_SUPPORTED);
533}
534
535#endif /* HAVE_LDAP */
536
Note: See TracBrowser for help on using the repository browser.