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