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