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

Last change on this file since 22 was 22, checked in by Yuri Dario, 14 years ago

Source code upgrade to 3.0.25pre2.

File size: 14.3 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;
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        context_handle = GSS_C_NO_CONTEXT;
372
373        input_token.value = NULL;
374        input_token.length = 0;
375
376        for (i=0; i < MAX_GSS_PASSES; i++) {
377                gss_rc = gss_init_sec_context(&minor_status,
378                                          GSS_C_NO_CREDENTIAL,
379                                          &context_handle,
380                                          serv_name,
381                                          mech_type,
382                                          GSS_C_MUTUAL_FLAG | GSS_C_REPLAY_FLAG,
383                                          0,
384                                          NULL,
385                                          &input_token,
386                                          NULL,
387                                          &output_token,
388                                          &ret_flags,
389                                          NULL);
390
391                if (input_token.value) {
392                        gss_release_buffer(&minor_status, &input_token);
393                }
394
395                if (gss_rc && gss_rc != GSS_S_CONTINUE_NEEDED) {
396                        status = ADS_ERROR_GSS(gss_rc, minor_status);
397                        goto failed;
398                }
399
400                cred.bv_val = (char *)output_token.value;
401                cred.bv_len = output_token.length;
402
403                rc = ldap_sasl_bind_s(ads->ld, NULL, "GSSAPI", &cred, NULL, NULL, 
404                                      &scred);
405                if (rc != LDAP_SASL_BIND_IN_PROGRESS) {
406                        status = ADS_ERROR(rc);
407                        goto failed;
408                }
409
410                if (output_token.value) {
411                        gss_release_buffer(&minor_status, &output_token);
412                }
413
414                if (scred) {
415                        input_token.value = scred->bv_val;
416                        input_token.length = scred->bv_len;
417                } else {
418                        input_token.value = NULL;
419                        input_token.length = 0;
420                }
421
422                if (gss_rc == 0) break;
423        }
424
425        gss_rc = gss_unwrap(&minor_status,context_handle,&input_token,&output_token,
426                            (int *)&conf_state,NULL);
427        if (gss_rc) {
428                status = ADS_ERROR_GSS(gss_rc, minor_status);
429                goto failed;
430        }
431
432        gss_release_buffer(&minor_status, &input_token);
433
434        p = (uint8 *)output_token.value;
435
436#if 0
437        file_save("sasl_gssapi.dat", output_token.value, output_token.length);
438#endif
439
440        if (p) {
441                max_msg_size = (p[1]<<16) | (p[2]<<8) | p[3];
442        }
443
444        gss_release_buffer(&minor_status, &output_token);
445
446        output_token.value = SMB_MALLOC(strlen(ads->config.bind_path) + 8);
447        p = (uint8 *)output_token.value;
448
449        *p++ = 1; /* no sign & seal selection */
450        /* choose the same size as the server gave us */
451        *p++ = max_msg_size>>16;
452        *p++ = max_msg_size>>8;
453        *p++ = max_msg_size;
454        snprintf((char *)p, strlen(ads->config.bind_path)+4, "dn:%s", ads->config.bind_path);
455        p += strlen((const char *)p);
456
457        output_token.length = PTR_DIFF(p, output_token.value);
458
459        gss_rc = gss_wrap(&minor_status, context_handle,0,GSS_C_QOP_DEFAULT,
460                          &output_token, (int *)&conf_state,
461                          &input_token);
462        if (gss_rc) {
463                status = ADS_ERROR_GSS(gss_rc, minor_status);
464                goto failed;
465        }
466
467        free(output_token.value);
468
469        cred.bv_val = (char *)input_token.value;
470        cred.bv_len = input_token.length;
471
472        rc = ldap_sasl_bind_s(ads->ld, NULL, "GSSAPI", &cred, NULL, NULL, 
473                              &scred);
474        status = ADS_ERROR(rc);
475
476        gss_release_buffer(&minor_status, &input_token);
477
478failed:
479
480        gss_release_name(&minor_status, &serv_name);
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.