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