]> git.ipfire.org Git - thirdparty/squid.git/blob - src/acl/external/kerberos_ldap_group/support_krb5.cc
kerberos_ldap_group: fix encryption type for cross realm check (#542)
[thirdparty/squid.git] / src / acl / external / kerberos_ldap_group / support_krb5.cc
1 /*
2 * Copyright (C) 1996-2020 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
27 * along with this program; if not, write to the Free Software
28 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA.
29 *
30 * -----------------------------------------------------------------------------
31 */
32
33 #include "squid.h"
34 #include "util.h"
35
36 #if HAVE_LDAP && HAVE_KRB5
37
38 #include "support.h"
39
40 #if HAVE_KRB5
41 extern struct kstruct kparam;
42 #endif
43
44 #define KT_PATH_MAX 256
45
46 void
47 krb5_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
58 static void
59 k5_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
73 static void
74 k5_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
88 static void
89 k5_error(const char* msg, krb5_error_code code)
90 {
91 k5_error2(msg, (char *)"", code);
92 }
93
94 /*
95 * create Kerberos memory cache
96 */
97 int
98 krb5_create_cache(char *domain, char *service_principal_name)
99 {
100
101 krb5_keytab keytab = NULL;
102 krb5_keytab_entry entry;
103 krb5_kt_cursor cursor;
104 krb5_cc_cursor ccursor;
105 krb5_creds *creds = NULL;
106 krb5_principal *principal_list = NULL;
107 krb5_principal principal = NULL;
108 char *service;
109 char *keytab_name = NULL, *principal_name = NULL, *mem_cache = NULL;
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 = NULL;
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 != NULL);
193 krb5_free_creds(kparam.context, creds);
194 creds = NULL;
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 = NULL;
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 != NULL);
226 krb5_free_creds(kparam.context, creds);
227 creds = NULL;
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 != NULL);
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 = NULL;
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 USE_HEIMDAL_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 USE_HEIMDAL_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 USE_HEIMDAL_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, NULL, NULL);
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, NULL, NULL, NULL, keytab, NULL, 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 = NULL;
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 = NULL;
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 = NULL;
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 = NULL;
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, NULL, NULL);
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, NULL, NULL, NULL, keytab, NULL, 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 USE_HEIMDAL_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 creds->keyblock.enctype = 0;
471 if (creds->keyblock.contents)
472 krb5_free_keyblock_contents(kparam.context, &creds->keyblock);
473
474 code = krb5_get_credentials(kparam.context, 0, kparam.cc[ccindex], creds, &tgt_creds);
475 if (code) {
476 k5_error("Error while getting tgt", code);
477 goto loop_end;
478 } else {
479 debug((char *) "%s| %s: DEBUG: Found trusted principal name: %s\n", LogTime(), PROGRAM, principal_name);
480 if (tgt_creds)
481 krb5_free_creds(kparam.context, tgt_creds);
482 tgt_creds = NULL;
483 break;
484 }
485
486 loop_end:
487 safe_free(principal_name);
488 if (tgt_creds)
489 krb5_free_creds(kparam.context, tgt_creds);
490 tgt_creds = NULL;
491 if (creds)
492 krb5_free_creds(kparam.context, creds);
493 creds = NULL;
494
495 }
496
497 if (creds)
498 krb5_free_creds(kparam.context, creds);
499 creds = NULL;
500 }
501 } else {
502 debug((char *) "%s| %s: DEBUG: Got principal from ccache\n", LogTime(), PROGRAM);
503 /*
504 * get credentials
505 */
506 code = krb5_unparse_name(kparam.context, principal, &principal_name);
507 if (code) {
508 k5_error("Error while unparsing principal name", code);
509 retval = 1;
510 goto cleanup;
511 }
512 debug((char *) "%s| %s: DEBUG: ccache has principal: %s\n", LogTime(), PROGRAM, principal_name);
513 }
514
515 if (!principal_name) {
516 debug((char *) "%s| %s: DEBUG: Got no principal name\n", LogTime(), PROGRAM);
517 retval = 1;
518 }
519 cleanup:
520 if (keytab)
521 krb5_kt_close(kparam.context, keytab);
522 xfree(keytab_name);
523 xfree(principal_name);
524 xfree(mem_cache);
525 if (principal)
526 krb5_free_principal(kparam.context, principal);
527 for (j = 0; j < nprinc; ++j) {
528 if (principal_list[j])
529 krb5_free_principal(kparam.context, principal_list[j]);
530 }
531 xfree(principal_list);
532 if (creds)
533 krb5_free_creds(kparam.context, creds);
534 return (retval);
535 }
536 #endif
537