]> git.ipfire.org Git - thirdparty/squid.git/blob - src/peer_proxy_negotiate_auth.cc
Merge from trunk
[thirdparty/squid.git] / src / peer_proxy_negotiate_auth.cc
1 /*
2 * Copyright (C) 1996-2014 The Squid Software Foundation and contributors
3 *
4 * Squid software is distributed under GPLv2+ license and includes
5 * contributions from numerous individuals and organizations.
6 * Please see the COPYING and CONTRIBUTORS files for details.
7 */
8
9 /*
10 * DEBUG: 11 Hypertext Transfer Protocol (HTTP)
11 */
12
13 #include "squid.h"
14
15 #if HAVE_KRB5 && HAVE_GSSAPI
16
17 #include "base64.h"
18 #include "Debug.h"
19 #include "peer_proxy_negotiate_auth.h"
20
21 #ifdef __cplusplus
22 extern "C" {
23 #endif
24
25 #if HAVE_PROFILE_H
26 #include <profile.h>
27 #endif /* HAVE_PROFILE_H */
28 #if HAVE_KRB5_H
29 #if HAVE_BROKEN_SOLARIS_KRB5_H
30 #if defined(__cplusplus)
31 #define KRB5INT_BEGIN_DECLS extern "C" {
32 #define KRB5INT_END_DECLS
33 KRB5INT_BEGIN_DECLS
34 #endif
35 #endif
36 #include <krb5.h>
37 #elif HAVE_ET_COM_ERR_H
38 #include <et/com_err.h>
39 #endif /* HAVE_COM_ERR_H */
40 #if HAVE_COM_ERR_H
41 #include <com_err.h>
42 #endif /* HAVE_COM_ERR_H */
43
44 #if HAVE_GSSAPI_GSSAPI_H
45 #include <gssapi/gssapi.h>
46 #elif HAVE_GSSAPI_H
47 #include <gssapi.h>
48 #endif /* HAVE_GSSAPI_H */
49 #if !USE_HEIMDAL_KRB5
50 #if HAVE_GSSAPI_GSSAPI_EXT_H
51 #include <gssapi/gssapi_ext.h>
52 #endif /* HAVE_GSSAPI_GSSAPI_EXT_H */
53 #if HAVE_GSSAPI_GSSAPI_KRB5_H
54 #include <gssapi/gssapi_krb5.h>
55 #endif /* HAVE_GSSAPI_GSSAPI_KRB5_H */
56 #if HAVE_GSSAPI_GSSAPI_GENERIC_H
57 #include <gssapi/gssapi_generic.h>
58 #endif /* HAVE_GSSAPI_GSSAPI_GENERIC_H */
59 #endif /* !USE_HEIMDAL_KRB5 */
60
61 #ifndef gss_nt_service_name
62 #define gss_nt_service_name GSS_C_NT_HOSTBASED_SERVICE
63 #endif
64
65 #if !HAVE_ERROR_MESSAGE && HAVE_KRB5_GET_ERR_MESSAGE
66 #define error_message(code) krb5_get_error_message(kparam.context,code)
67 #elif !HAVE_ERROR_MESSAGE && HAVE_KRB5_GET_ERROR_TEXT
68 #define error_message(code) krb5_get_err_text(kparam.context,code)
69 #elif !HAVE_ERROR_MESSAGE
70 static char err_code[17];
71 const char *KRB5_CALLCONV
72 error_message(long code) {
73 snprintf(err_code,16,"%ld",code);
74 return err_code;
75 }
76 #endif
77
78 #ifndef gss_mech_spnego
79 static gss_OID_desc _gss_mech_spnego =
80 { 6, (void *) "\x2b\x06\x01\x05\x05\x02" };
81 gss_OID gss_mech_spnego = &_gss_mech_spnego;
82 #endif
83
84 #if USE_IBM_KERBEROS
85 #include <ibm_svc/krb5_svc.h>
86 const char *KRB5_CALLCONV error_message(long code) {
87 char *msg = NULL;
88 krb5_svc_get_msg(code, &msg);
89 return msg;
90 }
91 #endif
92
93 /*
94 * Kerberos context and cache structure
95 * Caches authentication details to reduce
96 * number of authentication requests to kdc
97 */
98 static struct kstruct {
99 krb5_context context;
100 krb5_ccache cc;
101 } kparam = {
102 NULL, NULL};
103
104 /*
105 * krb5_create_cache creates a Kerberos file credential cache or a memory
106 * credential cache if supported. The initial key for the principal
107 * principal_name is extracted from the keytab keytab_filename.
108 *
109 * If keytab_filename is NULL the default will be used.
110 * If principal_name is NULL the first working entry of the keytab will be used.
111 */
112 int krb5_create_cache(char *keytab_filename, char *principal_name);
113
114 /*
115 * krb5_cleanup clears used Keberos memory
116 */
117 void krb5_cleanup(void);
118
119 /*
120 * check_gss_err checks for gssapi error codes, extracts the error message
121 * and prints it.
122 */
123 int check_gss_err(OM_uint32 major_status, OM_uint32 minor_status,
124 const char *function);
125
126 int check_gss_err(OM_uint32 major_status, OM_uint32 minor_status,
127 const char *function) {
128 if (GSS_ERROR(major_status)) {
129 OM_uint32 maj_stat, min_stat;
130 OM_uint32 msg_ctx = 0;
131 gss_buffer_desc status_string;
132 char buf[1024];
133 size_t len;
134
135 len = 0;
136 msg_ctx = 0;
137 while (!msg_ctx) {
138 /* convert major status code (GSS-API error) to text */
139 maj_stat = gss_display_status(&min_stat, major_status,
140 GSS_C_GSS_CODE, GSS_C_NULL_OID, &msg_ctx, &status_string);
141 if (maj_stat == GSS_S_COMPLETE) {
142 if (sizeof(buf) > len + status_string.length + 1) {
143 memcpy(buf + len, status_string.value,
144 status_string.length);
145 len += status_string.length;
146 }
147 gss_release_buffer(&min_stat, &status_string);
148 break;
149 }
150 gss_release_buffer(&min_stat, &status_string);
151 }
152 if (sizeof(buf) > len + 2) {
153 strcpy(buf + len, ". ");
154 len += 2;
155 }
156 msg_ctx = 0;
157 while (!msg_ctx) {
158 /* convert minor status code (underlying routine error) to text */
159 maj_stat = gss_display_status(&min_stat, minor_status,
160 GSS_C_MECH_CODE, GSS_C_NULL_OID, &msg_ctx, &status_string);
161 if (maj_stat == GSS_S_COMPLETE) {
162 if (sizeof(buf) > len + status_string.length) {
163 memcpy(buf + len, status_string.value,
164 status_string.length);
165 len += status_string.length;
166 }
167 gss_release_buffer(&min_stat, &status_string);
168 break;
169 }
170 gss_release_buffer(&min_stat, &status_string);
171 }
172 debugs(11, 5, HERE << function << "failed: " << buf);
173 return (1);
174 }
175 return (0);
176 }
177
178 void krb5_cleanup() {
179 debugs(11, 5, HERE << "Cleanup kerberos context");
180 if (kparam.context) {
181 if (kparam.cc)
182 krb5_cc_destroy(kparam.context, kparam.cc);
183 kparam.cc = NULL;
184 krb5_free_context(kparam.context);
185 kparam.context = NULL;
186 }
187 }
188
189 int krb5_create_cache(char *kf, char *pn) {
190
191 #define KT_PATH_MAX 256
192 #define MAX_RENEW_TIME "365d"
193 #define DEFAULT_SKEW (krb5_deltat) 600
194
195 static char *keytab_filename = NULL, *principal_name = NULL;
196 static krb5_keytab keytab = 0;
197 static krb5_keytab_entry entry;
198 static krb5_kt_cursor cursor;
199 static krb5_creds *creds = NULL;
200 #if USE_HEIMDAL_KRB5 && !HAVE_KRB5_GET_RENEWED_CREDS
201 static krb5_creds creds2;
202 #endif
203 static krb5_principal principal = NULL;
204 static krb5_deltat skew;
205
206 #if HAVE_KRB5_GET_INIT_CREDS_OPT_ALLOC
207 krb5_get_init_creds_opt *options;
208 #else
209 krb5_get_init_creds_opt options;
210 #endif
211 krb5_error_code code = 0;
212 krb5_deltat rlife;
213 #if HAVE_PROFILE_H && HAVE_KRB5_GET_PROFILE && HAVE_PROFILE_GET_INTEGER && HAVE_PROFILE_RELEASE
214 profile_t profile;
215 #endif
216 #if USE_HEIMDAL_KRB5 && !HAVE_KRB5_GET_RENEWED_CREDS
217 krb5_kdc_flags flags;
218 #if HAVE_KRB5_PRINCIPAL_GET_REALM
219 const char *client_realm;
220 #else
221 krb5_realm client_realm;
222 #endif
223 #endif
224 char *mem_cache;
225
226 restart:
227 /*
228 * Check if credentials need to be renewed
229 */
230 if (creds &&
231 (creds->times.endtime - time(0) > skew) &&
232 (creds->times.renew_till - time(0) > 2 * skew)) {
233 if (creds->times.endtime - time(0) < 2 * skew) {
234 #if HAVE_KRB5_GET_RENEWED_CREDS
235 /* renew ticket */
236 code =
237 krb5_get_renewed_creds(kparam.context, creds, principal,
238 kparam.cc, NULL);
239 #else
240 /* renew ticket */
241 flags.i = 0;
242 flags.b.renewable = flags.b.renew = 1;
243
244 code =
245 krb5_cc_get_principal(kparam.context, kparam.cc,
246 &creds2.client);
247 if (code) {
248 debugs(11, 5,
249 HERE <<
250 "Error while getting principal from credential cache : "
251 << error_message(code));
252 return (1);
253 }
254 #if HAVE_KRB5_PRINCIPAL_GET_REALM
255 client_realm = krb5_principal_get_realm(kparam.context, principal);
256 #else
257 client_realm = krb5_princ_realm(kparam.context, creds2.client);
258 #endif
259 code =
260 krb5_make_principal(kparam.context, &creds2.server,
261 (krb5_const_realm)&client_realm, KRB5_TGS_NAME,
262 (krb5_const_realm)&client_realm, NULL);
263 if (code) {
264 debugs(11, 5,
265 HERE << "Error while getting krbtgt principal : " <<
266 error_message(code));
267 return (1);
268 }
269 code =
270 krb5_get_kdc_cred(kparam.context, kparam.cc, flags, NULL,
271 NULL, &creds2, &creds);
272 krb5_free_creds(kparam.context, &creds2);
273 #endif
274 if (code) {
275 if (code == KRB5KRB_AP_ERR_TKT_EXPIRED) {
276 krb5_free_creds(kparam.context, creds);
277 creds = NULL;
278 /* this can happen because of clock skew */
279 goto restart;
280 }
281 debugs(11, 5,
282 HERE << "Error while get credentials : " <<
283 error_message(code));
284 return (1);
285 }
286 }
287 } else {
288 /* reinit */
289 if (!kparam.context) {
290 code = krb5_init_context(&kparam.context);
291 if (code) {
292 debugs(11, 5,
293 HERE << "Error while initialising Kerberos library : "
294 << error_message(code));
295 return (1);
296 }
297 }
298 #if HAVE_PROFILE_H && HAVE_KRB5_GET_PROFILE && HAVE_PROFILE_GET_INTEGER && HAVE_PROFILE_RELEASE
299 code = krb5_get_profile(kparam.context, &profile);
300 if (code) {
301 if (profile)
302 profile_release(profile);
303 debugs(11, 5,
304 HERE << "Error while getting profile : " <<
305 error_message(code));
306 return (1);
307 }
308 code =
309 profile_get_integer(profile, "libdefaults", "clockskew", 0,
310 5 * 60, &skew);
311 if (profile)
312 profile_release(profile);
313 if (code) {
314 debugs(11, 5,
315 HERE << "Error while getting clockskew : " <<
316 error_message(code));
317 return (1);
318 }
319 #elif USE_HEIMDAL_KRB5 && HAVE_KRB5_GET_MAX_TIME_SKEW
320 skew = krb5_get_max_time_skew(kparam.context);
321 #elif USE_HEIMDAL_KRB5 && HAVE_MAX_SKEW_IN_KRB5_CONTEXT
322 skew = kparam.context->max_skew;
323 #else
324 skew = DEFAULT_SKEW;
325 #endif
326
327 if (!kf) {
328 char buf[KT_PATH_MAX], *p;
329
330 krb5_kt_default_name(kparam.context, buf, KT_PATH_MAX);
331 p = strchr(buf, ':');
332 if (p)
333 ++p;
334 xfree(keytab_filename);
335 keytab_filename = xstrdup(p ? p : buf);
336 } else {
337 keytab_filename = xstrdup(kf);
338 }
339
340 code = krb5_kt_resolve(kparam.context, keytab_filename, &keytab);
341 if (code) {
342 debugs(11, 5,
343 HERE << "Error while resolving keytab filename " <<
344 keytab_filename << " : " << error_message(code));
345 return (1);
346 }
347
348 if (!pn) {
349 code = krb5_kt_start_seq_get(kparam.context, keytab, &cursor);
350 if (code) {
351 debugs(11, 5,
352 HERE << "Error while starting keytab scan : " <<
353 error_message(code));
354 return (1);
355 }
356 code =
357 krb5_kt_next_entry(kparam.context, keytab, &entry, &cursor);
358 krb5_copy_principal(kparam.context, entry.principal,
359 &principal);
360 if (code && code != KRB5_KT_END) {
361 debugs(11, 5,
362 HERE << "Error while scanning keytab : " <<
363 error_message(code));
364 return (1);
365 }
366
367 code = krb5_kt_end_seq_get(kparam.context, keytab, &cursor);
368 if (code) {
369 debugs(11, 5,
370 HERE << "Error while ending keytab scan : " <<
371 error_message(code));
372 return (1);
373 }
374 #if USE_HEIMDAL_KRB5 || ( HAVE_KRB5_KT_FREE_ENTRY && HAVE_DECL_KRB5_KT_FREE_ENTRY)
375 code = krb5_kt_free_entry(kparam.context, &entry);
376 #else
377 code = krb5_free_keytab_entry_contents(kparam.context, &entry);
378 #endif
379 if (code) {
380 debugs(11, 5,
381 HERE << "Error while freeing keytab entry : " <<
382 error_message(code));
383 return (1);
384 }
385
386 } else {
387 principal_name = xstrdup(pn);
388 }
389
390 if (!principal) {
391 code =
392 krb5_parse_name(kparam.context, principal_name, &principal);
393 if (code) {
394 debugs(11, 5,
395 HERE << "Error while parsing principal name " <<
396 principal_name << " : " << error_message(code));
397 return (1);
398 }
399 }
400
401 creds = (krb5_creds *) xmalloc(sizeof(*creds));
402 memset(creds, 0, sizeof(*creds));
403 #if HAVE_KRB5_GET_INIT_CREDS_OPT_ALLOC
404 krb5_get_init_creds_opt_alloc(kparam.context, &options);
405 #else
406 krb5_get_init_creds_opt_init(&options);
407 #endif
408 code = krb5_string_to_deltat((char *) MAX_RENEW_TIME, &rlife);
409 if (code != 0 || rlife == 0) {
410 debugs(11, 5,
411 HERE << "Error bad lifetime value " << MAX_RENEW_TIME <<
412 " : " << error_message(code));
413 return (1);
414 }
415 #if HAVE_KRB5_GET_INIT_CREDS_OPT_ALLOC
416 krb5_get_init_creds_opt_set_renew_life(options, rlife);
417 code =
418 krb5_get_init_creds_keytab(kparam.context, creds, principal,
419 keytab, 0, NULL, options);
420 #if HAVE_KRB5_GET_INIT_CREDS_FREE_CONTEXT
421 krb5_get_init_creds_opt_free(kparam.context, options);
422 #else
423 krb5_get_init_creds_opt_free(options);
424 #endif
425 #else
426 krb5_get_init_creds_opt_set_renew_life(&options, rlife);
427 code =
428 krb5_get_init_creds_keytab(kparam.context, creds, principal,
429 keytab, 0, NULL, &options);
430 #endif
431 if (code) {
432 debugs(11, 5,
433 HERE <<
434 "Error while initializing credentials from keytab : " <<
435 error_message(code));
436 return (1);
437 }
438 #if !HAVE_KRB5_MEMORY_CACHE
439 mem_cache =
440 (char *) xmalloc(strlen("FILE:/tmp/peer_proxy_negotiate_auth_")
441 + 16);
442 if (!mem_cache) {
443 debugs(11, 5, "Error while allocating memory");
444 return(1);
445 }
446 snprintf(mem_cache,
447 strlen("FILE:/tmp/peer_proxy_negotiate_auth_") + 16,
448 "FILE:/tmp/peer_proxy_negotiate_auth_%d", (int) getpid());
449 #else
450 mem_cache =
451 (char *) xmalloc(strlen("MEMORY:peer_proxy_negotiate_auth_") +
452 16);
453 if (!mem_cache) {
454 debugs(11, 5, "Error while allocating memory");
455 return(1);
456 }
457 snprintf(mem_cache,
458 strlen("MEMORY:peer_proxy_negotiate_auth_") + 16,
459 "MEMORY:peer_proxy_negotiate_auth_%d", (int) getpid());
460 #endif
461
462 setenv("KRB5CCNAME", mem_cache, 1);
463 code = krb5_cc_resolve(kparam.context, mem_cache, &kparam.cc);
464 xfree(mem_cache);
465 if (code) {
466 debugs(11, 5,
467 HERE << "Error while resolving memory credential cache : "
468 << error_message(code));
469 return (1);
470 }
471 code = krb5_cc_initialize(kparam.context, kparam.cc, principal);
472 if (code) {
473 debugs(11, 5,
474 HERE <<
475 "Error while initializing memory credential cache : " <<
476 error_message(code));
477 return (1);
478 }
479 code = krb5_cc_store_cred(kparam.context, kparam.cc, creds);
480 if (code) {
481 debugs(11, 5,
482 HERE << "Error while storing credentials : " <<
483 error_message(code));
484 return (1);
485 }
486
487 if (!creds->times.starttime)
488 creds->times.starttime = creds->times.authtime;
489 }
490 return (0);
491 }
492
493 /*
494 * peer_proxy_negotiate_auth gets a GSSAPI token for principal_name
495 * and base64 encodes it.
496 */
497 char *peer_proxy_negotiate_auth(char *principal_name, char *proxy) {
498 int rc = 0;
499 OM_uint32 major_status, minor_status;
500 gss_ctx_id_t gss_context = GSS_C_NO_CONTEXT;
501 gss_name_t server_name = GSS_C_NO_NAME;
502 gss_buffer_desc service = GSS_C_EMPTY_BUFFER;
503 gss_buffer_desc input_token = GSS_C_EMPTY_BUFFER;
504 gss_buffer_desc output_token = GSS_C_EMPTY_BUFFER;
505 char *token = NULL;
506
507 setbuf(stdout, NULL);
508 setbuf(stdin, NULL);
509
510 if (!proxy) {
511 debugs(11, 5, HERE << "Error : No proxy server name");
512 return NULL;
513 }
514
515 if (principal_name)
516 debugs(11, 5,
517 HERE << "Creating credential cache for " << principal_name);
518 else
519 debugs(11, 5, HERE << "Creating credential cache");
520 rc = krb5_create_cache(NULL, principal_name);
521 if (rc) {
522 debugs(11, 5, HERE << "Error : Failed to create Kerberos cache");
523 krb5_cleanup();
524 return NULL;
525 }
526
527 service.value = (void *) xmalloc(strlen("HTTP") + strlen(proxy) + 2);
528 snprintf((char *) service.value, strlen("HTTP") + strlen(proxy) + 2,
529 "%s@%s", "HTTP", proxy);
530 service.length = strlen((char *) service.value);
531
532 debugs(11, 5, HERE << "Import gss name");
533 major_status = gss_import_name(&minor_status, &service,
534 gss_nt_service_name, &server_name);
535
536 if (check_gss_err(major_status, minor_status, "gss_import_name()"))
537 goto cleanup;
538
539 debugs(11, 5, HERE << "Initialize gss security context");
540 major_status = gss_init_sec_context(&minor_status,
541 GSS_C_NO_CREDENTIAL,
542 &gss_context,
543 server_name,
544 gss_mech_spnego,
545 0,
546 0,
547 GSS_C_NO_CHANNEL_BINDINGS,
548 &input_token, NULL, &output_token, NULL, NULL);
549
550 if (check_gss_err(major_status, minor_status, "gss_init_sec_context()"))
551 goto cleanup;
552
553 debugs(11, 5, HERE << "Got token with length " << output_token.length);
554 if (output_token.length) {
555
556 token =
557 (char *) base64_encode_bin((const char *) output_token.value,
558 output_token.length);
559 }
560
561 cleanup:
562 gss_delete_sec_context(&minor_status, &gss_context, NULL);
563 gss_release_buffer(&minor_status, &service);
564 gss_release_buffer(&minor_status, &input_token);
565 gss_release_buffer(&minor_status, &output_token);
566 gss_release_name(&minor_status, &server_name);
567
568 return token;
569 }
570
571 #ifdef __cplusplus
572 }
573 #endif
574 #endif /* HAVE_KRB5 && HAVE_GSSAPI */