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