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