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

 

Introduction

Documentation

Support

Miscellaneous

Web Site Translations

Mirrors