Squid Web Cache v8/master
Loading...
Searching...
No Matches
support_krb5.cc
Go to the documentation of this file.
1/*
2 * Copyright (C) 1996-2026 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 * ----------------------------------------------------------------------------
11 *
12 * Author: Markus Moeller (markus_moeller at compuserve.com)
13 *
14 * Copyright (C) 2007 Markus Moeller. All rights reserved.
15 *
16 * This program is free software; you can redistribute it and/or modify
17 * it under the terms of the GNU General Public License as published by
18 * the Free Software Foundation; either version 2 of the License, or
19 * (at your option) any later version.
20 *
21 * This program is distributed in the hope that it will be useful,
22 * but WITHOUT ANY WARRANTY; without even the implied warranty of
23 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
24 * GNU General Public License for more details.
25 *
26 * You should have received a copy of the GNU General Public License along
27 * with this program; if not, see <https://www.gnu.org/licenses/>.
28 *
29 * -----------------------------------------------------------------------------
30 */
31
32#include "squid.h"
33
34#include <ctime>
35
36#if HAVE_LDAP && HAVE_KRB5
37
38#include "support.h"
39
40#if HAVE_KRB5
41extern struct kstruct kparam;
42#endif
43
44#define KT_PATH_MAX 256
45
46void
47krb5_cleanup()
48{
49 if (kparam.context)
50 for (int i=0; i<MAX_DOMAINS; i++) {
51 if (kparam.cc[i])
52 krb5_cc_destroy(kparam.context, kparam.cc[i]);
53 safe_free(kparam.mem_ccache[i]);
54 }
55 krb5_free_context(kparam.context);
56}
57
58static void
59k5_error2(const char* msg, char* msg2, krb5_error_code code)
60{
61 const char *errmsg;
62 errmsg = krb5_get_error_message(kparam.context, code);
63 error((char *) "%s| %s: ERROR: %s%s : %s\n", LogTime(), PROGRAM, msg, msg2, errmsg);
64#if HAVE_KRB5_FREE_ERROR_MESSAGE
65 krb5_free_error_message(kparam.context, errmsg);
66#elif HAVE_KRB5_FREE_ERROR_STRING
67 krb5_free_error_string(kparam.context, (char *)errmsg);
68#else
69 xfree(errmsg);
70#endif
71}
72
73static void
74k5_debug(const char* msg, krb5_error_code code)
75{
76 const char *errmsg;
77 errmsg = krb5_get_error_message(kparam.context, code);
78 debug((char *) "%s| %s: DEBUG: %s : %s\n", LogTime(), PROGRAM, msg, errmsg);
79#if HAVE_KRB5_FREE_ERROR_MESSAGE
80 krb5_free_error_message(kparam.context, errmsg);
81#elif HAVE_KRB5_FREE_ERROR_STRING
82 krb5_free_error_string(kparam.context, (char *)errmsg);
83#else
84 xfree(errmsg);
85#endif
86}
87
88static void
89k5_error(const char* msg, krb5_error_code code)
90{
91 k5_error2(msg, (char *)"", code);
92}
93
94/*
95 * create Kerberos memory cache
96 */
97int
98krb5_create_cache(char *domain, char *service_principal_name)
99{
100
101 krb5_keytab keytab = nullptr;
102 krb5_keytab_entry entry;
103 krb5_kt_cursor cursor;
104 krb5_cc_cursor ccursor;
105 krb5_creds *creds = nullptr;
106 krb5_principal *principal_list = nullptr;
107 krb5_principal principal = nullptr;
108 char *service;
109 char *keytab_name = nullptr, *principal_name = nullptr, *mem_cache = nullptr;
110 char buf[KT_PATH_MAX], *p;
111 size_t j,nprinc = 0;
112 int retval = 0;
113 krb5_error_code code = 0;
114 int ccindex=-1;
115
116 if (!domain || !strcmp(domain, ""))
117 return (1);
118
119 /*
120 * prepare memory credential cache
121 */
122#if !HAVE_KRB5_MEMORY_CACHE || HAVE_SUN_LDAP_SDK
123 mem_cache = (char *) xmalloc(strlen("FILE:/tmp/squid_ldap_") + strlen(domain) + 1 + 16);
124 snprintf(mem_cache, strlen("FILE:/tmp/squid_ldap_") + strlen(domain) + 1 + 16, "FILE:/tmp/squid_ldap_%s_%d", domain, (int) getpid());
125#else
126 mem_cache = (char *) xmalloc(strlen("MEMORY:squid_ldap_") + strlen(domain) + 1 + 16);
127 snprintf(mem_cache, strlen("MEMORY:squid_ldap_") + strlen(domain) + 1 + 16, "MEMORY:squid_ldap_%s_%d", domain, (int) getpid());
128#endif
129
130 setenv("KRB5CCNAME", mem_cache, 1);
131 debug((char *) "%s| %s: DEBUG: Set credential cache to %s\n", LogTime(), PROGRAM, mem_cache);
132 for (int i=0; i<MAX_DOMAINS; i++) {
133 if (kparam.mem_ccache[i] && !strcmp(mem_cache,kparam.mem_ccache[i])) {
134 ccindex=i;
135 break;
136 }
137 }
138 if ( ccindex == -1 ) {
139 kparam.mem_ccache[kparam.ncache]=xstrdup(mem_cache);
140 ccindex=kparam.ncache;
141 kparam.ncache++;
142 if ( kparam.ncache == MAX_DOMAINS ) {
143 error((char *) "%s| %s: ERROR: Too many domains to support: # domains %d\n", LogTime(), PROGRAM, kparam.ncache);
144 retval = 1;
145 goto cleanup;
146 }
147 code = krb5_cc_resolve(kparam.context, mem_cache, &kparam.cc[ccindex]);
148 if (code) {
149 k5_error("Error while resolving memory ccache", code);
150 retval = 1;
151 goto cleanup;
152 }
153 }
154 /*
155 * getting default principal from cache
156 */
157
158 code = krb5_cc_get_principal(kparam.context, kparam.cc[ccindex], &principal);
159 if (code) {
160 if (principal)
161 krb5_free_principal(kparam.context, principal);
162 principal = nullptr;
163 k5_debug("No default principal found in ccache", code);
164 } else {
165 /*
166 * Look for krbtgt and check if it is expired (or soon to be expired)
167 */
168 code = krb5_cc_start_seq_get(kparam.context, kparam.cc[ccindex], &ccursor);
169 if (code) {
170 k5_error("Error while starting ccache scan", code);
171 code = krb5_cc_close (kparam.context, kparam.cc[ccindex]);
172 if (code) {
173 k5_error("Error while closing ccache", code);
174 }
175 if (kparam.cc[ccindex]) {
176 code = krb5_cc_destroy(kparam.context, kparam.cc[ccindex]);
177 if (code) {
178 k5_error("Error while destroying ccache", code);
179 }
180 }
181 } else {
182 krb5_error_code code2 = 0;
183 creds = static_cast<krb5_creds *>(xcalloc(1,sizeof(*creds)));
184 while ((krb5_cc_next_cred(kparam.context, kparam.cc[ccindex], &ccursor, creds)) == 0) {
185 code2 = krb5_unparse_name(kparam.context, creds->server, &principal_name);
186 if (code2) {
187 k5_error("Error while unparsing principal", code2);
188 code = krb5_cc_destroy(kparam.context, kparam.cc[ccindex]);
189 if (code) {
190 k5_error("Error while destroying ccache", code);
191 }
192 assert(creds != nullptr);
193 krb5_free_creds(kparam.context, creds);
194 creds = nullptr;
195 safe_free(principal_name);
196 debug((char *) "%s| %s: DEBUG: Reset credential cache to %s\n", LogTime(), PROGRAM, mem_cache);
197 code = krb5_cc_resolve(kparam.context, mem_cache, &kparam.cc[ccindex]);
198 if (code) {
199 k5_error("Error while resolving memory ccache", code);
200 retval = 1;
201 goto cleanup;
202 }
203 code =1;
204 break;
205 }
206 if (!strncmp(KRB5_TGS_NAME,principal_name,KRB5_TGS_NAME_SIZE)) {
207 time_t now;
208 static krb5_deltat skew=MAX_SKEW;
209
210 debug((char *) "%s| %s: DEBUG: Found %s in cache : %s\n", LogTime(), PROGRAM,KRB5_TGS_NAME,principal_name);
211 /*
212 * Check time
213 */
214 time(&now);
215 debug((char *) "%s| %s: DEBUG: credential time diff %d\n", LogTime(), PROGRAM, (int)(creds->times.endtime - now));
216 if (creds->times.endtime - now < 2*skew) {
217 debug((char *) "%s| %s: DEBUG: credential will soon expire %d\n", LogTime(), PROGRAM, (int)(creds->times.endtime - now));
218 if (principal)
219 krb5_free_principal(kparam.context, principal);
220 principal = nullptr;
221 code = krb5_cc_destroy(kparam.context, kparam.cc[ccindex]);
222 if (code) {
223 k5_error("Error while destroying ccache", code);
224 }
225 assert(creds != nullptr);
226 krb5_free_creds(kparam.context, creds);
227 creds = nullptr;
228 safe_free(principal_name);
229 debug((char *) "%s| %s: DEBUG: Reset credential cache to %s\n", LogTime(), PROGRAM, mem_cache);
230 code = krb5_cc_resolve(kparam.context, mem_cache, &kparam.cc[ccindex]);
231 if (code) {
232 k5_error("Error while resolving ccache", code);
233 retval = 1;
234 goto cleanup;
235 }
236 code = 1;
237 } else {
238 safe_free(principal_name);
239 }
240 break;
241 }
242 assert(creds != nullptr);
243 krb5_free_creds(kparam.context, creds);
244 creds = static_cast<krb5_creds *>(xcalloc(1, sizeof(*creds)));
245 safe_free(principal_name);
246 }
247 if (creds)
248 krb5_free_creds(kparam.context, creds);
249 creds = nullptr;
250 code2 = krb5_cc_end_seq_get(kparam.context, kparam.cc[ccindex], &ccursor);
251 if (code2) {
252 k5_error("Error while ending ccache scan", code2);
253 retval = 1;
254 goto cleanup;
255 }
256 }
257 }
258 if (code) {
259 /*
260 * getting default keytab name
261 */
262
263 debug((char *) "%s| %s: DEBUG: Get default keytab file name\n", LogTime(), PROGRAM);
264 krb5_kt_default_name(kparam.context, buf, KT_PATH_MAX);
265 p = strchr(buf, ':'); /* Find the end if "FILE:" */
266 if (p)
267 ++p; /* step past : */
268 keytab_name = xstrdup(p ? p : buf);
269 debug((char *) "%s| %s: DEBUG: Got default keytab file name %s\n", LogTime(), PROGRAM, keytab_name);
270
271 code = krb5_kt_resolve(kparam.context, keytab_name, &keytab);
272 if (code) {
273 k5_error2("Error while resolving keytab ", keytab_name,code);
274 retval = 1;
275 goto cleanup;
276 }
277 code = krb5_kt_start_seq_get(kparam.context, keytab, &cursor);
278 if (code) {
279 k5_error("Error while starting keytab scan", code);
280 retval = 1;
281 goto cleanup;
282 }
283 debug((char *) "%s| %s: DEBUG: Get principal name from keytab %s\n", LogTime(), PROGRAM, keytab_name);
284
285 nprinc = 0;
286 while ((code = krb5_kt_next_entry(kparam.context, keytab, &entry, &cursor)) == 0) {
287 int found = 0;
288
289 principal_list = (krb5_principal *) xrealloc(principal_list, sizeof(krb5_principal) * (nprinc + 1));
290 krb5_copy_principal(kparam.context, entry.principal, &principal_list[nprinc++]);
291#if HAVE_LIBHEIMDAL_KRB5
292 debug((char *) "%s| %s: DEBUG: Keytab entry has realm name: %s\n", LogTime(), PROGRAM, entry.principal->realm);
293#else
294 debug((char *) "%s| %s: DEBUG: Keytab entry has realm name: %s\n", LogTime(), PROGRAM, krb5_princ_realm(kparam.context, entry.principal)->data);
295#endif
296#if HAVE_LIBHEIMDAL_KRB5
297 if (!strcasecmp(domain, entry.principal->realm))
298#else
299 if (!strcasecmp(domain, krb5_princ_realm(kparam.context, entry.principal)->data))
300#endif
301 {
302 code = krb5_unparse_name(kparam.context, entry.principal, &principal_name);
303 if (code) {
304 k5_error("Error while unparsing principal name", code);
305 } else {
306 debug((char *) "%s| %s: DEBUG: Found principal name: %s\n", LogTime(), PROGRAM, principal_name);
307 found = 1;
308 if (service_principal_name && strcasecmp(principal_name,service_principal_name) != 0 ) {
309 debug((char *) "%s| %s: DEBUG: principal name does not match parameter: %s\n", LogTime(), PROGRAM, service_principal_name);
310 safe_free(principal_name);
311 found = 0;
312 }
313 }
314 }
315#if HAVE_LIBHEIMDAL_KRB5 || ( HAVE_KRB5_KT_FREE_ENTRY && HAVE_DECL_KRB5_KT_FREE_ENTRY )
316 code = krb5_kt_free_entry(kparam.context, &entry);
317#else
318 code = krb5_free_keytab_entry_contents(kparam.context, &entry);
319#endif
320 if (code) {
321 k5_error("Error while freeing keytab entry", code);
322 retval = 1;
323 break;
324 }
325 if (found) {
326 debug((char *) "%s| %s: DEBUG: Got principal name %s\n", LogTime(), PROGRAM, principal_name);
327 /*
328 * build principal
329 */
330 code = krb5_parse_name(kparam.context, principal_name, &principal);
331 if (code) {
332 k5_error2("Error while parsing name ", principal_name,code);
333 safe_free(principal_name);
334 if (principal)
335 krb5_free_principal(kparam.context, principal);
336 found = 0;
337 continue;
338 }
339 creds = (krb5_creds *) xcalloc(1,sizeof(*creds));
340
341 /*
342 * get credentials
343 */
344#if HAVE_GET_INIT_CREDS_KEYTAB
345 code = krb5_get_init_creds_keytab(kparam.context, creds, principal, keytab, 0, nullptr, nullptr);
346#else
347 service = (char *) xmalloc(strlen("krbtgt") + 2 * strlen(domain) + 3);
348 snprintf(service, strlen("krbtgt") + 2 * strlen(domain) + 3, "krbtgt/%s@%s", domain, domain);
349 creds->client = principal;
350 code = krb5_parse_name(kparam.context, service, &creds->server);
351 xfree(service);
352 code = krb5_get_in_tkt_with_keytab(kparam.context, 0, nullptr, nullptr, nullptr, keytab, nullptr, creds, 0);
353#endif
354
355 if (code) {
356 k5_error("Error while initialising credentials from keytab", code);
357 safe_free(principal_name);
358 if (principal)
359 krb5_free_principal(kparam.context, principal);
360 if (creds)
361 krb5_free_creds(kparam.context, creds);
362 creds = nullptr;
363 found = 0;
364 continue;
365 }
366 code = krb5_cc_initialize(kparam.context, kparam.cc[ccindex], principal);
367 if (code) {
368 k5_error("Error while initialising cache", code);
369 safe_free(principal_name);
370 if (principal)
371 krb5_free_principal(kparam.context, principal);
372 if (creds)
373 krb5_free_creds(kparam.context, creds);
374 creds = nullptr;
375 found = 0;
376 continue;
377 }
378 code = krb5_cc_store_cred(kparam.context, kparam.cc[ccindex], creds);
379 if (code) {
380 k5_error("Error while storing credentials", code);
381 if (principal)
382 krb5_free_principal(kparam.context, principal);
383 safe_free(principal_name);
384 if (creds)
385 krb5_free_creds(kparam.context, creds);
386 creds = nullptr;
387 found = 0;
388 continue;
389 }
390 debug((char *) "%s| %s: DEBUG: Stored credentials\n", LogTime(), PROGRAM);
391 break;
392 }
393 }
394
395 if (code && code != KRB5_KT_END) {
396 k5_error("Error while scanning keytab", code);
397 retval = 1;
398 goto cleanup;
399 }
400 code = krb5_kt_end_seq_get(kparam.context, keytab, &cursor);
401 if (code) {
402 k5_error("Error while ending keytab scan", code);
403 retval = 1;
404 goto cleanup;
405 }
406
407 /*
408 * if no principal name found in keytab for domain use the prinipal name which can get a TGT
409 */
410 if (!principal_name && !service_principal_name) {
411 size_t i;
412 debug((char *) "%s| %s: DEBUG: Did not find a principal in keytab for domain %s.\n", LogTime(), PROGRAM, domain);
413 debug((char *) "%s| %s: DEBUG: Try to get principal of trusted domain.\n", LogTime(), PROGRAM);
414
415 for (i = 0; i < nprinc; ++i) {
416 krb5_creds *tgt_creds = nullptr;
417 creds = (krb5_creds *) xmalloc(sizeof(*creds));
418 memset(creds, 0, sizeof(*creds));
419 /*
420 * get credentials
421 */
422 code = krb5_unparse_name(kparam.context, principal_list[i], &principal_name);
423 if (code) {
424 k5_error("Error while unparsing principal name", code);
425 goto loop_end;
426 }
427 debug((char *) "%s| %s: DEBUG: Keytab entry has principal: %s\n", LogTime(), PROGRAM, principal_name);
428
429#if HAVE_GET_INIT_CREDS_KEYTAB
430 code = krb5_get_init_creds_keytab(kparam.context, creds, principal_list[i], keytab, 0, nullptr, nullptr);
431#else
432 service = (char *) xmalloc(strlen("krbtgt") + 2 * strlen(domain) + 3);
433 snprintf(service, strlen("krbtgt") + 2 * strlen(domain) + 3, "krbtgt/%s@%s", domain, domain);
434 creds->client = principal_list[i];
435 code = krb5_parse_name(kparam.context, service, &creds->server);
436 xfree(service);
437 code = krb5_get_in_tkt_with_keytab(kparam.context, 0, nullptr, nullptr, nullptr, keytab, nullptr, creds, 0);
438#endif
439 if (code) {
440 k5_error("Error while initialising credentials from keytab", code);
441 goto loop_end;
442 }
443 code = krb5_cc_initialize(kparam.context, kparam.cc[ccindex], principal_list[i]);
444 if (code) {
445 k5_error("Error while initialising memory caches", code);
446 goto loop_end;
447 }
448 code = krb5_cc_store_cred(kparam.context, kparam.cc[ccindex], creds);
449 if (code) {
450 k5_error("Error while storing credentials", code);
451 goto loop_end;
452 }
453 if (creds->server)
454 krb5_free_principal(kparam.context, creds->server);
455#if HAVE_LIBHEIMDAL_KRB5
456 service = (char *) xmalloc(strlen("krbtgt") + strlen(domain) + strlen(principal_list[i]->realm) + 3);
457 snprintf(service, strlen("krbtgt") + strlen(domain) + strlen(principal_list[i]->realm) + 3, "krbtgt/%s@%s", domain, principal_list[i]->realm);
458#else
459 service = (char *) xmalloc(strlen("krbtgt") + strlen(domain) + strlen(krb5_princ_realm(kparam.context, principal_list[i])->data) + 3);
460 snprintf(service, strlen("krbtgt") + strlen(domain) + strlen(krb5_princ_realm(kparam.context, principal_list[i])->data) + 3, "krbtgt/%s@%s", domain, krb5_princ_realm(kparam.context, principal_list[i])->data);
461#endif
462 code = krb5_parse_name(kparam.context, service, &creds->server);
463 xfree(service);
464 if (code) {
465 k5_error("Error while initialising TGT credentials", code);
466 goto loop_end;
467 }
468
469 // overwrite limitation of enctypes
470#if HAVE_LIBHEIMDAL_KRB5
471 creds->session.keytype = 0;
472 if (creds->session.keyvalue.length > 0)
473 krb5_free_keyblock_contents(kparam.context, &creds->session);
474#else
475 creds->keyblock.enctype = 0;
476 if (creds->keyblock.contents)
477 krb5_free_keyblock_contents(kparam.context, &creds->keyblock);
478#endif
479 code = krb5_get_credentials(kparam.context, 0, kparam.cc[ccindex], creds, &tgt_creds);
480 if (code) {
481 k5_error("Error while getting tgt", code);
482 goto loop_end;
483 } else {
484 debug((char *) "%s| %s: DEBUG: Found trusted principal name: %s\n", LogTime(), PROGRAM, principal_name);
485 if (tgt_creds)
486 krb5_free_creds(kparam.context, tgt_creds);
487 tgt_creds = nullptr;
488 break;
489 }
490
491loop_end:
492 safe_free(principal_name);
493 if (tgt_creds)
494 krb5_free_creds(kparam.context, tgt_creds);
495 tgt_creds = nullptr;
496 if (creds)
497 krb5_free_creds(kparam.context, creds);
498 creds = nullptr;
499
500 }
501
502 if (creds)
503 krb5_free_creds(kparam.context, creds);
504 creds = nullptr;
505 }
506 } else {
507 debug((char *) "%s| %s: DEBUG: Got principal from ccache\n", LogTime(), PROGRAM);
508 /*
509 * get credentials
510 */
511 code = krb5_unparse_name(kparam.context, principal, &principal_name);
512 if (code) {
513 k5_error("Error while unparsing principal name", code);
514 retval = 1;
515 goto cleanup;
516 }
517 debug((char *) "%s| %s: DEBUG: ccache has principal: %s\n", LogTime(), PROGRAM, principal_name);
518 }
519
520 if (!principal_name) {
521 debug((char *) "%s| %s: DEBUG: Got no principal name\n", LogTime(), PROGRAM);
522 retval = 1;
523 }
524cleanup:
525 if (keytab)
526 krb5_kt_close(kparam.context, keytab);
527 xfree(keytab_name);
528 xfree(principal_name);
529 xfree(mem_cache);
530 if (principal)
531 krb5_free_principal(kparam.context, principal);
532 for (j = 0; j < nprinc; ++j) {
533 if (principal_list[j])
534 krb5_free_principal(kparam.context, principal_list[j]);
535 }
536 xfree(principal_list);
537 if (creds)
538 krb5_free_creds(kparam.context, creds);
539 return (retval);
540}
541#endif
542
void error(char *format,...)
#define PROGRAM
Definition support.h:168
const char * LogTime(void)
#define assert(EX)
Definition assert.h:17
void debug(const char *format,...)
Definition debug.cc:19
#define xfree
#define xstrdup
#define xmalloc
void * xrealloc(void *s, size_t sz)
Definition xalloc.cc:126
void * xcalloc(size_t n, size_t sz)
Definition xalloc.cc:71
#define safe_free(x)
Definition xalloc.h:73