]>
Commit | Line | Data |
---|---|---|
5d0a7127 | 1 | /* |
2 | * Copyright (C) 1998-2000 Luke Howard. All rights reserved. | |
3 | * CONFIDENTIAL | |
4 | * $Id$ | |
5 | * | |
6 | * Implementation of GSS-API client side binding for SASL | |
7 | */ | |
8 | ||
9 | #ifdef _WIN32 | |
10 | #include <windows.h> | |
11 | #endif | |
12 | #include <stdio.h> | |
13 | #include <string.h> | |
14 | #include <sys/types.h> | |
15 | #ifndef _WIN32 | |
16 | #include <sys/uio.h> | |
cd9e6b16 | 17 | #include <sys/socket.h> |
5d0a7127 | 18 | #include <netinet/in.h> |
19 | #endif | |
20 | ||
cd9e6b16 | 21 | #include <arpa/nameser.h> |
22 | #include <resolv.h> | |
23 | #include <lber.h> | |
5d0a7127 | 24 | #include <krb5.h> |
25 | #include "ldap-int.h" | |
26 | #include "gssldap-int.h" | |
27 | ||
28 | #ifndef _WIN32 | |
29 | typedef gss_uint32 OM_uint32; | |
30 | #endif | |
31 | ||
cd9e6b16 | 32 | char ldap_domain_name[128]; |
33 | ||
34 | #ifndef T_SRV | |
35 | #define T_SRV 33 | |
36 | #endif | |
37 | #define LDAP_SERVICE "_ldap" | |
38 | #define TCP_PROTOCOL "_tcp" | |
39 | ||
40 | int locate_ldap_server(char *domain, char **server_name); | |
5d0a7127 | 41 | int ldap_delete_tickets(LDAP *ld, char *service_name); |
cd9e6b16 | 42 | static int negotiate_security_options(gssldap_client_state_t state, |
43 | int layer); | |
5d0a7127 | 44 | int ldap_reset_principal(LDAP *ld, char *service_name, gss_name_t target_name); |
cd9e6b16 | 45 | unsigned long gsssasl_pack_security_token(OM_uint32 *min_stat, gss_ctx_id_t context, |
46 | gsssasl_security_negotiation_t inmask, | |
47 | size_t masklength, gss_buffer_t wrapped_tok); | |
48 | ||
5d0a7127 | 49 | int gsssasl_unpack_security_token(OM_uint32 *min_stat, gss_ctx_id_t context, |
cd9e6b16 | 50 | gss_buffer_t wrapped_tok, |
51 | gsssasl_security_negotiation_t *outmask, | |
52 | size_t *masklength); | |
5d0a7127 | 53 | |
54 | #ifdef GSSSASL_DEBUG | |
55 | static int debug_ = 1; | |
56 | #define TRACE(x) do { if (debug_) fprintf(stderr, "%s\n", x); fflush(stderr); } while (0); | |
57 | #define LDAP_PERROR(ld, x) do { if (debug_) ldap_perror(ld, x); } while (0); | |
58 | #define LOG_STATUS(msg, min, maj) log_status(msg, min, maj) | |
59 | #else | |
60 | #define TRACE(x) | |
61 | #define LDAP_PERROR(ld, x) | |
62 | #define LOG_STATUS(msg, min, maj) | |
cd9e6b16 | 63 | #endif /* GSSSASL_DEBUG */ |
5d0a7127 | 64 | |
65 | #ifdef HACK_SERVICE_NAME | |
66 | char *__service = NULL; | |
67 | #endif | |
68 | ||
69 | /* | |
70 | * The read and write fns need to access the context in here and | |
71 | * there doesn't appear to be a way to pass this info to them. | |
72 | */ | |
73 | static gss_ctx_id_t security_context; | |
74 | static int security_layer = 0; | |
75 | static int security_token_size = 0; | |
76 | ||
cd9e6b16 | 77 | LDAP_CALLBACK int LDAP_C ldap_gssapi_read(LBER_SOCKET sock, void *data, |
78 | int len) | |
79 | { | |
80 | OM_uint32 maj_stat; | |
81 | OM_uint32 min_stat; | |
82 | OM_uint32 rc; | |
83 | OM_uint32 pdulen; | |
84 | gss_buffer_desc recv_tok; | |
85 | gss_buffer_desc wrapped_tok; | |
86 | ||
87 | if (security_layer & | |
88 | (GSSSASL_INTEGRITY_PROTECTION | GSSSASL_PRIVACY_PROTECTION)) | |
89 | { | |
90 | if (recv(sock, (char *)&pdulen, sizeof(pdulen), 0) != sizeof(pdulen)) | |
91 | return (0); | |
92 | wrapped_tok.length = ntohl(pdulen); | |
93 | #ifdef GSSSASL_DEBUG | |
94 | if (debug_) | |
95 | { | |
96 | fprintf(stderr, "Reading data of %d octets\n", | |
97 | wrapped_tok.length); | |
98 | } | |
99 | #endif /* GSSSASL_DEBUG */ | |
100 | if ((int)wrapped_tok.length > security_token_size) | |
101 | return (0); | |
102 | wrapped_tok.value = malloc(wrapped_tok.length); | |
103 | if (wrapped_tok.value == NULL) | |
104 | return (0); | |
105 | if (recv(sock, wrapped_tok.value, wrapped_tok.length, 0) | |
106 | != (int)wrapped_tok.length) | |
107 | { | |
108 | gss_release_buffer(&rc, &wrapped_tok); | |
109 | return (0); | |
110 | } | |
111 | maj_stat = gss_unwrap(&min_stat, | |
112 | security_context, | |
113 | &wrapped_tok, | |
114 | &recv_tok, | |
115 | &rc, | |
116 | (gss_qop_t *) NULL); | |
117 | if (maj_stat != GSS_S_COMPLETE) | |
118 | { | |
119 | gss_release_buffer(&rc, &wrapped_tok); | |
120 | return maj_stat; | |
121 | } | |
122 | #ifdef GSSSASL_DEBUG | |
123 | if (debug_) | |
124 | { | |
125 | fprintf(stderr, "Got %d bytes of %s data\n", | |
126 | recv_tok.length, rc ? "private" : "signed"); | |
127 | } | |
128 | #endif /* GSSSASL_DEBUG */ | |
129 | if ((int)recv_tok.length > len) | |
130 | { | |
131 | gss_release_buffer(&rc, &recv_tok); | |
132 | gss_release_buffer(&rc, &wrapped_tok); | |
133 | return GSS_S_FAILURE; | |
134 | } | |
135 | memcpy(data, recv_tok.value, recv_tok.length); | |
136 | pdulen = recv_tok.length; | |
137 | gss_release_buffer(&rc, &recv_tok); | |
138 | gss_release_buffer(&rc, &wrapped_tok); | |
139 | ||
140 | return pdulen; | |
141 | } | |
142 | else | |
143 | return read(sock, data, len); | |
144 | } | |
145 | ||
146 | LDAP_CALLBACK int LDAP_C ldap_gssapi_write(LBER_SOCKET sock, const void *data, | |
147 | int len) | |
148 | { | |
149 | OM_uint32 maj_stat; | |
150 | OM_uint32 min_stat; | |
151 | OM_uint32 pdulen; | |
152 | OM_uint32 rc; | |
153 | gss_buffer_desc send_tok; | |
154 | gss_buffer_desc wrapped_tok; | |
155 | unsigned char *ptr; | |
156 | ||
157 | if (security_layer & | |
158 | (GSSSASL_INTEGRITY_PROTECTION | GSSSASL_PRIVACY_PROTECTION)) | |
159 | { | |
160 | send_tok.length = len; | |
161 | send_tok.value = (void *) data; | |
162 | #ifdef GSSSASL_DEBUG | |
163 | if (debug_) | |
164 | { | |
165 | fprintf(stderr, "Sending %d bytes of data\n", | |
166 | len); | |
167 | } | |
168 | #endif /* GSSSASL_DEBUG */ | |
169 | maj_stat = gss_wrap(&min_stat, | |
170 | security_context, | |
171 | (security_layer & GSSSASL_PRIVACY_PROTECTION) ? 1 : 0, | |
172 | GSS_C_QOP_DEFAULT, | |
173 | &send_tok, | |
174 | &rc, | |
175 | &wrapped_tok); | |
176 | if (maj_stat != GSS_S_COMPLETE) | |
177 | return maj_stat; | |
178 | #ifdef GSSSASL_DEBUG | |
179 | if (debug_) | |
180 | { | |
181 | fprintf(stderr, "Sent %d bytes of %s data\n", | |
182 | wrapped_tok.length, rc ? "private" : "signed"); | |
183 | } | |
184 | #endif /* GSSSASL_DEBUG */ | |
185 | pdulen = htonl(wrapped_tok.length); | |
186 | ptr = calloc(1, sizeof(pdulen) + wrapped_tok.length); | |
187 | memcpy(ptr, &pdulen, sizeof(pdulen)); | |
188 | memcpy(&ptr[sizeof(pdulen)], wrapped_tok.value, wrapped_tok.length); | |
189 | rc = send(sock, ptr, sizeof(pdulen) + wrapped_tok.length, 0); | |
190 | free(ptr); | |
191 | if (rc == (wrapped_tok.length + sizeof(pdulen))) | |
192 | rc = len; | |
193 | gss_release_buffer(&maj_stat, &wrapped_tok); | |
194 | return rc; | |
195 | } | |
196 | else | |
197 | return send(sock, data, len, 0); | |
198 | } | |
199 | ||
5d0a7127 | 200 | #ifdef GSSSASL_DEBUG |
201 | /* | |
202 | * Dump the token to stderr | |
203 | */ | |
204 | static void print_token(gss_buffer_t tok) | |
205 | { | |
206 | int i; | |
207 | unsigned char *p = tok->value; | |
cd9e6b16 | 208 | |
5d0a7127 | 209 | for (i = 0; i < (int)tok->length; i++, p++) |
210 | { | |
211 | fprintf(stderr, "%02x ", *p); | |
212 | if ((i % 16) == 15) | |
cd9e6b16 | 213 | { |
214 | fprintf(stderr, "\n"); | |
215 | } | |
5d0a7127 | 216 | } |
217 | fprintf(stderr, "\n"); | |
218 | fflush(stderr); | |
219 | return; | |
220 | } | |
221 | ||
222 | /* | |
223 | * Handle a GSS-API error | |
224 | */ | |
225 | static void log_status_impl(const char *reason, OM_uint32 res, int type) | |
226 | { | |
227 | OM_uint32 maj_stat, min_stat; | |
228 | gss_buffer_desc msg; | |
229 | OM_uint32 msg_ctx; | |
230 | ||
231 | msg_ctx = 0; | |
232 | while (1) | |
233 | { | |
cd9e6b16 | 234 | maj_stat = gss_display_status(&min_stat, |
235 | res, | |
236 | type, | |
237 | GSS_C_NULL_OID, | |
238 | &msg_ctx, | |
239 | &msg); | |
5d0a7127 | 240 | |
241 | if (debug_) | |
cd9e6b16 | 242 | { |
243 | fprintf(stderr, "ldap_adgssapi_bind: %s: %s\n", | |
244 | (char *) msg.value, | |
245 | reason); | |
246 | } | |
5d0a7127 | 247 | (void) gss_release_buffer(&min_stat, &msg); |
248 | if (!msg_ctx) | |
cd9e6b16 | 249 | break; |
5d0a7127 | 250 | } |
251 | return; | |
252 | } | |
253 | ||
254 | /* | |
255 | * Cover function to handle a GSS-API error | |
256 | */ | |
cd9e6b16 | 257 | static void log_status(const char *reason, OM_uint32 maj_stat, |
258 | OM_uint32 min_stat) | |
5d0a7127 | 259 | { |
260 | log_status_impl(reason, maj_stat, GSS_C_GSS_CODE); | |
261 | log_status_impl(reason, min_stat, GSS_C_MECH_CODE); | |
262 | return; | |
263 | } | |
cd9e6b16 | 264 | #endif /* GSSSASL_DEBUG */ |
5d0a7127 | 265 | |
266 | /* | |
267 | * Send a GSS-API token as part of a SASL BindRequest | |
268 | */ | |
269 | static int send_token(gssldap_client_state_t state, gss_buffer_t send_tok) | |
270 | { | |
271 | struct berval cred; | |
272 | ||
273 | TRACE("==> send_token"); | |
274 | ||
275 | cred.bv_val = send_tok->value; | |
276 | cred.bv_len = send_tok->length; | |
277 | ||
cd9e6b16 | 278 | if (ldap_sasl_bind(state->ld, |
279 | state->binddn, | |
280 | GSSAPI_SASL_NAME, | |
281 | &cred, | |
282 | NULL, | |
283 | NULL, | |
284 | &state->msgid) != LDAP_SUCCESS) | |
285 | { | |
5d0a7127 | 286 | LDAP_PERROR(state->ld, "send_token"); |
287 | TRACE("<== send_token"); | |
288 | return -1; | |
289 | } | |
290 | TRACE("<== send_token"); | |
291 | return 0; | |
292 | } | |
293 | ||
294 | /* | |
295 | * Parse the final result sent back from the server. | |
296 | */ | |
297 | static int parse_bind_result(gssldap_client_state_t state) | |
298 | { | |
299 | LDAPMessage *res = NULL, *msg = NULL; | |
300 | int rc; | |
301 | ||
302 | TRACE("==> parse_bind_result"); | |
303 | ||
cd9e6b16 | 304 | if (ldap_result(state->ld, |
305 | state->msgid, | |
306 | LDAP_MSG_ALL, | |
307 | NULL, | |
308 | &res) <= 0) | |
5d0a7127 | 309 | { |
310 | LDAP_PERROR(state->ld, "ldap_result"); | |
311 | TRACE("<== parse_bind_result"); | |
312 | return -1; | |
313 | } | |
cd9e6b16 | 314 | for (msg = ldap_first_message(state->ld, res); |
315 | msg != NULL; | |
316 | msg = ldap_next_message(state->ld, msg)) | |
5d0a7127 | 317 | { |
318 | if (ldap_msgtype(msg) == LDAP_RES_BIND) | |
cd9e6b16 | 319 | { |
320 | ldap_parse_result(state->ld, msg, &rc, NULL, NULL, NULL, NULL, 0); | |
321 | break; | |
322 | } | |
323 | } | |
5d0a7127 | 324 | |
325 | ldap_msgfree(res); | |
326 | state->msgid = -1; | |
cd9e6b16 | 327 | |
5d0a7127 | 328 | TRACE("<== parse_bind_result"); |
cd9e6b16 | 329 | |
5d0a7127 | 330 | if (rc == LDAP_SUCCESS) |
cd9e6b16 | 331 | { |
332 | return 0; | |
333 | } | |
5d0a7127 | 334 | state->rc = rc; |
335 | return -1; | |
336 | } | |
337 | ||
338 | /* | |
339 | * Receive a GSS-API token from a SASL BindResponse | |
340 | * The contents of recv_tok must be freed by the | |
341 | * caller. | |
342 | */ | |
343 | static int recv_token(gssldap_client_state_t state, gss_buffer_t recv_tok) | |
344 | { | |
345 | struct berval *servercred = NULL; | |
346 | LDAPMessage *res = NULL, *msg = NULL; | |
347 | int rc; | |
348 | ||
349 | TRACE("==> recv_token"); | |
350 | ||
351 | if (ldap_result(state->ld, state->msgid, LDAP_MSG_ALL, NULL, &res) <= 0) | |
352 | { | |
353 | LDAP_PERROR(state->ld, "ldap_result"); | |
354 | TRACE("<== recv_token"); | |
355 | return -1; | |
356 | } | |
357 | recv_tok->value = NULL; | |
358 | recv_tok->length = 0; | |
359 | ||
cd9e6b16 | 360 | for (msg = ldap_first_message(state->ld, res); |
361 | msg != NULL; | |
362 | msg = ldap_next_message(state->ld, msg)) | |
5d0a7127 | 363 | { |
cd9e6b16 | 364 | if (ldap_msgtype(msg) == LDAP_RES_BIND && servercred == NULL) |
365 | { | |
366 | rc = ldap_parse_sasl_bind_result(state->ld, | |
367 | msg, | |
368 | &servercred, | |
369 | 0); | |
370 | if (rc == LDAP_SUCCESS && servercred != NULL) | |
371 | { | |
372 | recv_tok->value = malloc(servercred->bv_len); | |
373 | if (recv_tok->value != NULL) | |
374 | { | |
375 | memcpy(recv_tok->value, servercred->bv_val, servercred->bv_len); | |
376 | recv_tok->length = servercred->bv_len; | |
377 | } | |
378 | break; | |
379 | } | |
380 | else | |
381 | { | |
382 | state->rc = rc; | |
383 | } | |
384 | } | |
385 | } | |
5d0a7127 | 386 | |
387 | ldap_msgfree(res); | |
388 | state->msgid = -1; | |
389 | TRACE("<== recv_token"); | |
390 | if (servercred != NULL) | |
391 | { | |
cd9e6b16 | 392 | nslberi_free(servercred); |
5d0a7127 | 393 | TRACE("<== recv_token"); |
394 | return 0; | |
cd9e6b16 | 395 | } |
5d0a7127 | 396 | if (state->rc == LDAP_SUCCESS) |
cd9e6b16 | 397 | { |
398 | state->rc = LDAP_OPERATIONS_ERROR; | |
399 | } | |
5d0a7127 | 400 | else |
cd9e6b16 | 401 | { |
402 | LDAP_PERROR(state->ld, "recv_token"); | |
403 | } | |
404 | ||
5d0a7127 | 405 | TRACE("<== recv_token"); |
406 | return -1; | |
407 | } | |
408 | ||
409 | /* | |
410 | * The client calls GSS_Init_sec_context, passing in 0 for | |
411 | * input_context_handle (initially) and a targ_name equal to output_name | |
412 | * from GSS_Import_Name called with input_name_type of | |
413 | * GSS_C_NT_HOSTBASED_SERVICE and input_name_string of | |
414 | * "service@hostname" where "service" is the service name specified in | |
415 | * the protocol's profile, and "hostname" is the fully qualified host | |
416 | * name of the server. The client then responds with the resulting | |
417 | * output_token. If GSS_Init_sec_context returns GSS_S_CONTINUE_NEEDED, | |
418 | * then the client should expect the server to issue a token in a | |
419 | * subsequent challenge. The client must pass the token to another call | |
420 | * to GSS_Init_sec_context, repeating the actions in this paragraph. | |
421 | */ | |
422 | static int client_establish_context(LDAP *ld, gssldap_client_state_t state, | |
cd9e6b16 | 423 | char *service_name) |
5d0a7127 | 424 | { |
425 | gss_buffer_desc send_tok; | |
426 | gss_buffer_desc recv_tok; | |
427 | gss_buffer_desc *token_ptr; | |
cd9e6b16 | 428 | gss_name_t target_name; |
429 | OM_uint32 maj_stat; | |
430 | OM_uint32 min_stat; | |
431 | gss_OID oid = GSS_C_NULL_OID; | |
432 | OM_uint32 ret_flags; | |
5d0a7127 | 433 | |
434 | TRACE("==> client_establish_context"); | |
435 | ||
436 | memset(&recv_tok, '\0', sizeof(recv_tok)); | |
437 | send_tok.value = service_name; | |
438 | send_tok.length = strlen(send_tok.value) + 1; | |
439 | ||
cd9e6b16 | 440 | maj_stat = gss_import_name(&min_stat, |
441 | &send_tok, | |
442 | (gss_OID) gss_nt_service_name, | |
443 | &target_name); | |
5d0a7127 | 444 | if (ldap_reset_principal(ld, service_name, target_name)) |
445 | { | |
446 | TRACE("<== client_establish_context"); | |
447 | return -1; | |
448 | } | |
449 | ||
450 | token_ptr = GSS_C_NO_BUFFER; | |
451 | state->context = GSS_C_NO_CONTEXT; | |
cd9e6b16 | 452 | |
5d0a7127 | 453 | do |
454 | { | |
cd9e6b16 | 455 | maj_stat = gss_init_sec_context(&min_stat, |
456 | GSS_C_NO_CREDENTIAL, | |
457 | &state->context, | |
458 | target_name, | |
459 | oid, | |
460 | GSS_C_MUTUAL_FLAG | GSS_C_REPLAY_FLAG, | |
461 | 0, | |
462 | NULL, | |
463 | token_ptr, | |
464 | NULL, | |
465 | &send_tok, | |
466 | &ret_flags, | |
467 | NULL); | |
5d0a7127 | 468 | |
469 | if (token_ptr != GSS_C_NO_BUFFER) | |
cd9e6b16 | 470 | (void) gss_release_buffer(&min_stat, &recv_tok); |
5d0a7127 | 471 | |
472 | if (maj_stat != GSS_S_COMPLETE && maj_stat != GSS_S_CONTINUE_NEEDED) | |
cd9e6b16 | 473 | { |
474 | LOG_STATUS("initializing context", maj_stat, min_stat); | |
475 | (void) gss_release_name(&min_stat, &target_name); | |
476 | TRACE("<== client_establish_context"); | |
477 | return -1; | |
478 | } | |
5d0a7127 | 479 | #ifdef GSSSASL_DEBUG |
480 | if (debug_) | |
cd9e6b16 | 481 | { |
482 | fprintf(stderr, "Sending init_sec_context token (size=%d)...\n", | |
483 | send_tok.length); | |
484 | fflush(stderr); | |
485 | print_token(&send_tok); | |
486 | } | |
5d0a7127 | 487 | #endif |
488 | if (send_token(state, &send_tok) < 0) | |
cd9e6b16 | 489 | { |
490 | TRACE("Send_token failed"); | |
491 | (void) gss_release_buffer(&min_stat, &send_tok); | |
492 | (void) gss_release_name(&min_stat, &target_name); | |
493 | TRACE("<== client_establish_context"); | |
494 | return -1; | |
495 | } | |
5d0a7127 | 496 | (void) gss_release_buffer(&min_stat, &send_tok); |
497 | if (maj_stat == GSS_S_CONTINUE_NEEDED) | |
cd9e6b16 | 498 | { |
499 | TRACE("continue needed..."); | |
500 | if (recv_token(state, &recv_tok) < 0) | |
501 | { | |
502 | TRACE("recv_token failed"); | |
503 | (void) gss_release_name(&min_stat, &target_name); | |
504 | TRACE("<== client_establish_context"); | |
505 | return -1; | |
506 | } | |
5d0a7127 | 507 | #ifdef GSSSASL_DEBUG |
cd9e6b16 | 508 | if (debug_) |
509 | { | |
510 | fprintf(stderr, "Received token (size=%d)...\n", | |
511 | recv_tok.length); | |
512 | fflush(stderr); | |
513 | print_token(&recv_tok); | |
514 | } | |
5d0a7127 | 515 | #endif |
cd9e6b16 | 516 | token_ptr = &recv_tok; |
517 | } | |
5d0a7127 | 518 | } while (maj_stat == GSS_S_CONTINUE_NEEDED); |
519 | ||
520 | (void) gss_release_name(&min_stat, &target_name); | |
cd9e6b16 | 521 | |
5d0a7127 | 522 | TRACE("<== client_establish_context"); |
523 | return 0; | |
524 | } | |
525 | ||
526 | /* | |
527 | * When GSS_Init_sec_context returns GSS_S_COMPLETE, the client takes | |
528 | * the following actions: If the last call to GSS_Init_sec_context | |
529 | * returned an output_token, then the client responds with the | |
530 | * output_token, otherwise the client responds with no data. The client | |
531 | * should then expect the server to issue a token in a subsequent | |
532 | * challenge. The client passes this token to GSS_Unwrap and interprets | |
533 | * the first octet of resulting cleartext as a bit-mask specifying the | |
534 | * security layers supported by the server and the second through fourth | |
535 | * octets as the maximum size output_message to send to the server. The | |
536 | * client then constructs data, with the first octet containing the | |
537 | * bit-mask specifying the selected security layer, the second through | |
538 | * fourth octets containing in network byte order the maximum size | |
539 | * output_message the client is able to receive, and the remaining | |
540 | * octets containing the authorization identity. The client passes the | |
541 | * data to GSS_Wrap with conf_flag set to FALSE, and responds with the | |
542 | * generated output_message. The client can then consider the server | |
543 | * authenticated. | |
544 | */ | |
cd9e6b16 | 545 | static int negotiate_security_options(gssldap_client_state_t state, |
546 | int layer) | |
5d0a7127 | 547 | { |
cd9e6b16 | 548 | OM_uint32 maj_stat; |
549 | OM_uint32 min_stat; | |
5d0a7127 | 550 | gss_buffer_desc recv_tok; |
551 | gss_buffer_desc send_tok; | |
cd9e6b16 | 552 | OM_uint32 rc; |
553 | size_t mask_length; | |
554 | gsssasl_security_negotiation_t send_mask; | |
555 | gsssasl_security_negotiation_t recv_mask; | |
556 | ||
5d0a7127 | 557 | TRACE("==> negotiate_security_options"); |
cd9e6b16 | 558 | |
5d0a7127 | 559 | memset(&send_tok, '\0', sizeof(send_tok)); |
560 | memset(&recv_tok, '\0', sizeof(recv_tok)); | |
561 | if ((rc = recv_token(state, &recv_tok)) < 0) | |
562 | { | |
563 | TRACE("<== negotiate_security_options (recv_token failed)"); | |
564 | return rc; | |
565 | } | |
566 | #ifdef GSSSASL_DEBUG | |
567 | if (debug_) | |
cd9e6b16 | 568 | { |
569 | fprintf(stderr, "Received token (size=%d)...\n", | |
570 | recv_tok.length); | |
571 | fflush(stderr); | |
572 | print_token(&recv_tok); | |
5d0a7127 | 573 | } |
cd9e6b16 | 574 | #endif /* GSSSASL_DEBUG */ |
575 | ||
576 | maj_stat = gsssasl_unpack_security_token(&min_stat, | |
577 | state->context, | |
578 | &recv_tok, | |
579 | &recv_mask, | |
580 | &mask_length); | |
581 | ||
582 | if (maj_stat != GSS_S_COMPLETE) | |
583 | { | |
584 | LOG_STATUS("unpacking security negotiation token", | |
585 | maj_stat, | |
586 | min_stat); | |
587 | TRACE("<== negotiate_security_options (unpack failed)"); | |
588 | return -1; | |
589 | } | |
5d0a7127 | 590 | #ifdef GSSSASL_DEBUG |
cd9e6b16 | 591 | if (debug_) |
592 | { | |
593 | fprintf(stderr, "Received security token level %d size %d\n", | |
594 | recv_mask->security_layer, | |
595 | recv_mask->token_size); | |
5d0a7127 | 596 | } |
597 | #endif | |
cd9e6b16 | 598 | if ((~recv_mask->security_layer) & layer) |
599 | { | |
600 | free(recv_mask); | |
601 | TRACE("<== negotiate_security_options (unsupported security layer)"); | |
602 | return -1; | |
5d0a7127 | 603 | } |
cd9e6b16 | 604 | mask_length = sizeof(recv_mask) + |
605 | GSSAPI_LDAP_DN_PREFIX_LEN + strlen(state->binddn); | |
606 | send_mask = NSLDAPI_MALLOC(mask_length); | |
607 | if (send_mask == NULL) | |
608 | { | |
609 | free(recv_mask); | |
610 | TRACE("<== negotiate_security_options (malloc failed)"); | |
611 | return -1; | |
612 | } | |
613 | send_mask->security_layer = layer; | |
614 | send_mask->token_size = recv_mask->token_size; | |
615 | memcpy(send_mask->identity, GSSAPI_LDAP_DN_PREFIX, GSSAPI_LDAP_DN_PREFIX_LEN); | |
616 | memcpy(send_mask->identity + GSSAPI_LDAP_DN_PREFIX_LEN, | |
617 | state->binddn, | |
618 | mask_length - sizeof(recv_mask) - GSSAPI_LDAP_DN_PREFIX_LEN); | |
619 | free(recv_mask); | |
620 | ||
5d0a7127 | 621 | #ifdef GSSSASL_DEBUG |
cd9e6b16 | 622 | if (debug_) |
623 | { | |
624 | fprintf(stderr, "Sending security token level %d size %d\n", | |
625 | send_mask->security_layer, | |
626 | send_mask->token_size); | |
627 | } | |
5d0a7127 | 628 | #endif |
cd9e6b16 | 629 | maj_stat = gsssasl_pack_security_token(&min_stat, |
630 | state->context, | |
631 | send_mask, | |
632 | mask_length, | |
633 | &send_tok); | |
634 | if (maj_stat != GSS_S_COMPLETE) | |
635 | { | |
636 | LOG_STATUS("packing security negotiation token", maj_stat, min_stat); | |
637 | NSLDAPI_FREE(send_mask); | |
638 | TRACE("<== negotiate_security_options (pack failed)"); | |
639 | return -1; | |
640 | } | |
641 | if ((rc = send_token(state, &send_tok)) < 0) | |
642 | { | |
643 | NSLDAPI_FREE(send_mask); | |
644 | TRACE("<== negotiate_security_options (send_token failed)"); | |
645 | return rc; | |
646 | } | |
647 | rc = parse_bind_result(state); | |
648 | ||
649 | if (rc == 0) | |
650 | { | |
651 | security_context = state->context; | |
652 | security_layer = layer; | |
653 | security_token_size = send_mask->token_size; | |
5d0a7127 | 654 | } |
5d0a7127 | 655 | |
cd9e6b16 | 656 | NSLDAPI_FREE(send_mask); |
657 | if (send_tok.value != NULL) | |
658 | gss_release_buffer(&rc, &send_tok); | |
659 | if (recv_tok.value != NULL) | |
660 | gss_release_buffer(&rc, &recv_tok); | |
661 | TRACE("<== negotiate_security_options"); | |
662 | return rc; | |
5d0a7127 | 663 | } |
664 | ||
665 | #ifdef GSSSASL_DEBUG | |
666 | /* | |
667 | * Temporary function to enable debugging | |
668 | */ | |
669 | LDAP_API(int) | |
670 | LDAP_CALL ldap_gssapi_debug(int on) | |
671 | { | |
672 | int old = debug_; | |
673 | debug_ = on; | |
674 | return old; | |
675 | } | |
cd9e6b16 | 676 | #endif /* GSSSASL_DEBUG */ |
5d0a7127 | 677 | |
678 | /* | |
679 | * Public function for doing a GSS-API SASL bind | |
680 | */ | |
681 | LDAP_API(int) | |
682 | LDAP_CALL ldap_adgssapi_bind(LDAP *ld, const char *who, int layer) | |
683 | { | |
684 | gssldap_client_state_desc state; | |
cd9e6b16 | 685 | char *service_name; |
686 | OM_uint32 min_stat; | |
687 | int rc; | |
688 | int i; | |
689 | struct ldap_io_fns iofns; | |
5d0a7127 | 690 | |
691 | if (!NSLDAPI_VALID_LDAP_POINTER(ld) || ld->ld_defhost == NULL) | |
cd9e6b16 | 692 | { |
693 | return -1; | |
694 | } | |
695 | ||
5d0a7127 | 696 | for (i = 0; i < (int)strlen(ld->ld_defhost); i++) |
697 | ld->ld_defhost[i] = toupper(ld->ld_defhost[i]); | |
698 | ||
cd9e6b16 | 699 | service_name = |
700 | NSLDAPI_MALLOC(sizeof(GSSAPI_LDAP_SERVICE_NAME "@") + strlen(ld->ld_defhost)); | |
5d0a7127 | 701 | if (service_name == NULL) |
cd9e6b16 | 702 | { |
703 | return -1; | |
704 | } | |
5d0a7127 | 705 | strcpy(service_name, GSSAPI_LDAP_SERVICE_NAME "@"); |
706 | strcat(service_name, ld->ld_defhost); | |
707 | ||
708 | #ifdef GSSSASL_DEBUG | |
cd9e6b16 | 709 | if (debug_) |
710 | { | |
711 | fprintf(stderr, "LDAP service name: %s\n", service_name); | |
712 | fflush(stderr); | |
713 | } | |
5d0a7127 | 714 | #endif |
715 | ||
cd9e6b16 | 716 | state.msgid = -1; |
717 | state.ld = ld; | |
718 | state.binddn = who; | |
719 | state.context = GSS_C_NO_CONTEXT; | |
720 | state.rc = LDAP_OPERATIONS_ERROR; | |
5d0a7127 | 721 | |
cd9e6b16 | 722 | rc = client_establish_context(ld, &state, service_name); |
723 | if (rc == 0) | |
724 | { | |
5d0a7127 | 725 | rc = negotiate_security_options(&state, layer); |
cd9e6b16 | 726 | } |
727 | if (rc == 0) | |
728 | { | |
5d0a7127 | 729 | #ifdef GSSSASL_DEBUG |
cd9e6b16 | 730 | gss_buffer_desc tname; |
731 | gss_buffer_desc sname; | |
732 | gss_name_t targ_name; | |
733 | gss_name_t src_name; | |
734 | OM_uint32 context_flags; | |
735 | OM_uint32 lifetime; | |
736 | OM_uint32 maj_stat; | |
737 | int is_open; | |
738 | int is_local; | |
739 | gss_OID mechanism; | |
740 | gss_OID name_type; | |
741 | ||
742 | maj_stat = gss_inquire_context(&min_stat, | |
743 | state.context, | |
744 | &src_name, | |
745 | &targ_name, | |
746 | &lifetime, | |
747 | &mechanism, | |
748 | &context_flags, | |
749 | &is_local, | |
750 | &is_open); | |
751 | if (maj_stat != GSS_S_COMPLETE) | |
752 | { | |
753 | LOG_STATUS("inquiring context", maj_stat, min_stat); | |
754 | return LDAP_OPERATIONS_ERROR; | |
755 | } | |
756 | maj_stat = gss_display_name(&min_stat, | |
757 | src_name, | |
758 | &sname, | |
759 | &name_type); | |
760 | if (maj_stat != GSS_S_COMPLETE) | |
761 | { | |
762 | LOG_STATUS("displaying source name", maj_stat, min_stat); | |
763 | return LDAP_OPERATIONS_ERROR; | |
764 | } | |
765 | maj_stat = gss_display_name(&min_stat, | |
766 | targ_name, | |
767 | &tname, | |
768 | (gss_OID *) NULL); | |
769 | if (maj_stat != GSS_S_COMPLETE) | |
770 | { | |
771 | LOG_STATUS("displaying target name", maj_stat, min_stat); | |
772 | return LDAP_OPERATIONS_ERROR; | |
773 | } | |
774 | if (debug_) | |
775 | { | |
776 | fprintf(stderr, | |
777 | "\"%.*s\" to \"%.*s\", lifetime %d, flags %x, %s, %s\n", | |
778 | (int) sname.length, (char *) sname.value, | |
779 | (int) tname.length, (char *) tname.value, lifetime, | |
780 | context_flags, | |
781 | (is_local) ? "locally initiated" : "remotely initiated", | |
782 | (is_open) ? "open" : "closed"); | |
783 | fflush(stderr); | |
784 | } | |
785 | (void) gss_release_name(&min_stat, &src_name); | |
786 | (void) gss_release_name(&min_stat, &targ_name); | |
787 | (void) gss_release_buffer(&min_stat, &sname); | |
788 | (void) gss_release_buffer(&min_stat, &tname); | |
5d0a7127 | 789 | #endif |
cd9e6b16 | 790 | state.rc = LDAP_SUCCESS; |
791 | } | |
5d0a7127 | 792 | |
cd9e6b16 | 793 | if (state.rc == LDAP_SUCCESS) |
794 | { | |
b9fa546a | 795 | if (layer == GSSSASL_PRIVACY_PROTECTION) |
cd9e6b16 | 796 | { |
797 | memset(&iofns, 0, sizeof(iofns)); | |
798 | iofns.liof_read = ldap_gssapi_read; | |
799 | iofns.liof_write = ldap_gssapi_write; | |
800 | state.rc = ldap_set_option(ld, LDAP_OPT_IO_FN_PTRS, &iofns); | |
801 | } | |
802 | } | |
803 | ||
804 | NSLDAPI_FREE(service_name); | |
805 | LDAP_SET_LDERRNO(ld, state.rc, NULL, NULL); | |
806 | ||
807 | return state.rc; | |
5d0a7127 | 808 | } |
809 | ||
810 | /* Wrap and encode a security negotiation token */ | |
cd9e6b16 | 811 | unsigned long gsssasl_pack_security_token(OM_uint32 *min_stat, |
812 | gss_ctx_id_t context, | |
813 | gsssasl_security_negotiation_t inmask, | |
814 | size_t masklength, gss_buffer_t wrapped_tok) | |
5d0a7127 | 815 | { |
cd9e6b16 | 816 | OM_uint32 maj_stat; |
817 | OM_uint32 rc; | |
5d0a7127 | 818 | gss_buffer_desc send_tok; |
819 | ||
820 | wrapped_tok->length = 0; | |
821 | wrapped_tok->value = NULL; | |
822 | ||
823 | if (masklength < sizeof(gsssasl_security_negotiation_desc)) | |
824 | return GSS_S_FAILURE; | |
825 | ||
826 | inmask->token_size = htonl(inmask->token_size); | |
827 | ||
828 | send_tok.length = masklength; | |
829 | send_tok.value = inmask; | |
830 | ||
cd9e6b16 | 831 | maj_stat = gss_wrap(min_stat, |
832 | context, | |
833 | 0, | |
834 | GSS_C_QOP_DEFAULT, | |
835 | &send_tok, | |
836 | (int *)&rc, | |
837 | wrapped_tok); | |
5d0a7127 | 838 | |
839 | inmask->token_size = ntohl(inmask->token_size); | |
840 | ||
841 | return maj_stat; | |
842 | } | |
843 | ||
844 | /* Unwrap and decode a security negotiation token. */ | |
cd9e6b16 | 845 | int gsssasl_unpack_security_token(OM_uint32 *min_stat, gss_ctx_id_t context, |
846 | gss_buffer_t wrapped_tok, | |
847 | gsssasl_security_negotiation_t *outmask, | |
848 | size_t *masklength) | |
5d0a7127 | 849 | { |
cd9e6b16 | 850 | OM_uint32 maj_stat; |
851 | OM_uint32 rc; | |
5d0a7127 | 852 | gss_buffer_desc recv_tok; |
853 | ||
854 | *masklength = 0; | |
855 | *outmask = NULL; | |
856 | memset(&recv_tok, '\0', sizeof(recv_tok)); | |
857 | ||
cd9e6b16 | 858 | maj_stat = gss_unwrap(min_stat, |
859 | context, | |
860 | wrapped_tok, | |
861 | &recv_tok, | |
862 | (int *)&rc, | |
863 | (gss_qop_t *) NULL); | |
5d0a7127 | 864 | if (maj_stat != GSS_S_COMPLETE) |
865 | return maj_stat; | |
866 | ||
cd9e6b16 | 867 | /* |
5d0a7127 | 868 | #ifdef _WIN32 |
869 | if (recv_tok.length < sizeof(gsssasl_security_negotiation_desc)) | |
870 | { | |
871 | gss_release_buffer(&rc, &recv_tok); | |
872 | return GSS_S_FAILURE; | |
873 | } | |
cd9e6b16 | 874 | #endif |
875 | */ | |
5d0a7127 | 876 | |
cd9e6b16 | 877 | /* |
878 | * we're lazy and don't copy the token. This could cause | |
879 | * problems if libgssapi uses a different malloc! | |
880 | */ | |
5d0a7127 | 881 | *masklength = recv_tok.length; |
882 | *outmask = (gsssasl_security_negotiation_t) recv_tok.value; | |
883 | if (*outmask == NULL) | |
884 | { | |
885 | gss_release_buffer(&rc, &recv_tok); | |
886 | return GSS_S_FAILURE; | |
887 | } | |
888 | return GSS_S_COMPLETE; | |
889 | } | |
890 | ||
891 | int ldap_reset_principal(LDAP *ld, char *service_name, gss_name_t target_name) | |
892 | { | |
cd9e6b16 | 893 | krb5_context context = NULL; |
894 | krb5_principal princ; | |
895 | krb5_principal princ1; | |
896 | krb5_principal temp_princ; | |
897 | char *realm; | |
898 | char *server_name = NULL; | |
899 | int i; | |
5d0a7127 | 900 | |
5d0a7127 | 901 | princ = (krb5_principal)(target_name); |
cd9e6b16 | 902 | |
5d0a7127 | 903 | if (krb5_init_context(&context)) |
cd9e6b16 | 904 | return(-1); |
905 | ||
906 | realm = strdup(ldap_domain_name); | |
907 | for (i = 0; i < (int)strlen(realm); i++) | |
908 | realm[i] = toupper(realm[i]); | |
909 | server_name = strdup(ld->ld_defhost); | |
910 | for (i = 0; i < (int)strlen(server_name); i++) | |
911 | server_name[i] = tolower(server_name[i]); | |
5d0a7127 | 912 | |
5d0a7127 | 913 | temp_princ = malloc(sizeof(*princ)); |
914 | memcpy(temp_princ, princ, sizeof(*princ)); | |
915 | ||
cd9e6b16 | 916 | krb5_build_principal(context, &princ1, strlen(realm), realm, "ldap", |
917 | server_name, ldap_domain_name, 0); | |
5d0a7127 | 918 | |
919 | memcpy(princ, princ1, sizeof(*princ1)); | |
920 | free(princ1); | |
921 | krb5_free_principal(context, temp_princ); | |
cd9e6b16 | 922 | |
923 | if (realm != NULL) | |
924 | free(realm); | |
925 | if (server_name != NULL) | |
926 | free(server_name); | |
5d0a7127 | 927 | if (context != NULL) |
928 | krb5_free_context(context); | |
cd9e6b16 | 929 | return(0); |
5d0a7127 | 930 | } |
931 | ||
932 | int ldap_delete_tickets(LDAP *ld, char *service_name) | |
933 | { | |
cd9e6b16 | 934 | int i; |
935 | int rc; | |
936 | krb5_context context = NULL; | |
937 | krb5_ccache v5Cache = NULL; | |
938 | krb5_creds creds; | |
939 | krb5_cc_cursor v5Cursor; | |
5d0a7127 | 940 | krb5_error_code code; |
cd9e6b16 | 941 | char *sServerName; |
5d0a7127 | 942 | |
943 | if (!NSLDAPI_VALID_LDAP_POINTER(ld) || ld->ld_defhost == NULL) | |
cd9e6b16 | 944 | { |
945 | return -1; | |
946 | } | |
5d0a7127 | 947 | |
948 | for (i = 0; i < (int)strlen(ld->ld_defhost); i++) | |
949 | ld->ld_defhost[i] = toupper(ld->ld_defhost[i]); | |
950 | ||
951 | rc = -1; | |
952 | ||
953 | if (krb5_init_context(&context)) | |
954 | return(rc); | |
955 | ||
956 | if (krb5_cc_default(context, &v5Cache)) | |
957 | goto cleanup; | |
958 | if (krb5_cc_start_seq_get(context, v5Cache, &v5Cursor)) | |
959 | goto cleanup; | |
960 | ||
961 | memset(&creds, '\0', sizeof(creds)); | |
962 | ||
963 | while (!(code = krb5_cc_next_cred(context, v5Cache, &v5Cursor, &creds))) | |
964 | { | |
965 | if (krb5_unparse_name(context, creds.server, &sServerName)) | |
cd9e6b16 | 966 | { |
967 | krb5_free_cred_contents(context, &creds); | |
968 | continue; | |
969 | } | |
5d0a7127 | 970 | if (!memcmp(sServerName, service_name, strlen(service_name))) |
cd9e6b16 | 971 | { |
972 | krb5_cc_remove_cred(context, v5Cache, 0, &creds); | |
973 | } | |
5d0a7127 | 974 | continue; |
975 | } | |
cd9e6b16 | 976 | |
5d0a7127 | 977 | if ((code == KRB5_CC_END) || (code == KRB5_CC_NOTFOUND)) |
cd9e6b16 | 978 | { |
979 | krb5_cc_end_seq_get(context, v5Cache, &v5Cursor); | |
980 | } | |
5d0a7127 | 981 | rc = 0; |
982 | ||
983 | cleanup: | |
984 | if ((v5Cache != NULL) && (context != NULL)) | |
985 | krb5_cc_close(context, v5Cache); | |
986 | if (context != NULL) | |
987 | krb5_free_context(context); | |
988 | return(rc); | |
989 | } | |
cd9e6b16 | 990 | |
991 | int locate_ldap_server(char *domain, char **server_name) | |
992 | { | |
993 | char service[128]; | |
994 | char host[128]; | |
995 | int location_type; | |
996 | int length; | |
997 | int rc; | |
998 | int return_code; | |
999 | int entry_length; | |
1000 | int server_count; | |
1001 | unsigned char reply[1024]; | |
1002 | unsigned char *ptr; | |
1003 | ||
1004 | strcpy(ldap_domain_name, domain); | |
1005 | sprintf(service, "%s.%s.%s.", LDAP_SERVICE, TCP_PROTOCOL, domain); | |
1006 | ||
1007 | return_code = -1; | |
1008 | server_count = 0; | |
1009 | memset(reply, '\0', sizeof(reply)); | |
1010 | length = res_search(service, C_IN, T_SRV, reply, sizeof(reply)); | |
1011 | if (length >= 0) | |
1012 | { | |
1013 | ptr = reply; | |
1014 | ptr += sizeof(HEADER); | |
1015 | if ((rc = dn_expand(reply, reply + length, ptr, host, | |
1016 | sizeof(host))) < 0) | |
1017 | return(-1); | |
1018 | ptr += (rc + 4); | |
1019 | ||
1020 | while (ptr < reply + length) | |
1021 | { | |
1022 | if ((rc = dn_expand(reply, reply + length, ptr, host, | |
1023 | sizeof(host))) < 0) | |
1024 | break; | |
1025 | ptr += rc; | |
1026 | location_type = (ptr[0] << 8) | ptr[1]; | |
1027 | ptr += 8; | |
1028 | entry_length = (ptr[0] << 8) | ptr[1]; | |
1029 | ptr += 2; | |
1030 | if (location_type == T_SRV) | |
1031 | { | |
1032 | if ((rc = dn_expand(reply, reply + length, ptr + 6, host, | |
1033 | sizeof(host))) < 0) | |
1034 | return -1; | |
1035 | ||
1036 | (*server_name) = strdup(host); | |
1037 | ++server_name; | |
1038 | return_code = 1; | |
1039 | server_count++; | |
1040 | } | |
1041 | ptr += entry_length; | |
1042 | } | |
1043 | } | |
1044 | return(return_code); | |
1045 | } |