]> git.ipfire.org Git - thirdparty/squid.git/blob - helpers/external_acl/kerberos_ldap_group/support_krb5.cc
SourceFormat Enforcement
[thirdparty/squid.git] / helpers / external_acl / kerberos_ldap_group / support_krb5.cc
1 /*
2 * Copyright (C) 1996-2015 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 * create Kerberos memory cache
59 */
60 int
61 krb5_create_cache(char *domain)
62 {
63
64 krb5_keytab keytab = NULL;
65 krb5_keytab_entry entry;
66 krb5_kt_cursor cursor;
67 krb5_cc_cursor ccursor;
68 krb5_creds *creds = NULL;
69 krb5_principal *principal_list = NULL;
70 krb5_principal principal = NULL;
71 char *service;
72 char *keytab_name = NULL, *principal_name = NULL, *mem_cache = NULL;
73 char buf[KT_PATH_MAX], *p;
74 size_t j,nprinc = 0;
75 int retval = 0;
76 krb5_error_code code = 0;
77 int ccindex=-1;
78
79 if (!domain || !strcmp(domain, ""))
80 return (1);
81
82 /*
83 * prepare memory credential cache
84 */
85 #if !HAVE_KRB5_MEMORY_CACHE || HAVE_SUN_LDAP_SDK
86 mem_cache = (char *) xmalloc(strlen("FILE:/tmp/squid_ldap_") + strlen(domain) + 1 + 16);
87 snprintf(mem_cache, strlen("FILE:/tmp/squid_ldap_") + strlen(domain) + 1 + 16, "FILE:/tmp/squid_ldap_%s_%d", domain, (int) getpid());
88 #else
89 mem_cache = (char *) xmalloc(strlen("MEMORY:squid_ldap_") + strlen(domain) + 1 + 16);
90 snprintf(mem_cache, strlen("MEMORY:squid_ldap_") + strlen(domain) + 1 + 16, "MEMORY:squid_ldap_%s_%d", domain, (int) getpid());
91 #endif
92
93 setenv("KRB5CCNAME", mem_cache, 1);
94 debug((char *) "%s| %s: DEBUG: Set credential cache to %s\n", LogTime(), PROGRAM, mem_cache);
95 for (int i=0; i<MAX_DOMAINS; i++) {
96 if (kparam.mem_ccache[i] && !strcmp(mem_cache,kparam.mem_ccache[i])) {
97 ccindex=i;
98 break;
99 }
100 }
101 if ( ccindex == -1 ) {
102 kparam.mem_ccache[kparam.ncache]=xstrdup(mem_cache);
103 ccindex=kparam.ncache;
104 kparam.ncache++;
105 if ( kparam.ncache == MAX_DOMAINS ) {
106 error((char *) "%s| %s: ERROR: Too many domains to support: # domains %d\n", LogTime(), PROGRAM, kparam.ncache);
107 retval = 1;
108 goto cleanup;
109 }
110 code = krb5_cc_resolve(kparam.context, mem_cache, &kparam.cc[ccindex]);
111 if (code) {
112 error((char *) "%s| %s: ERROR: Error while resolving memory ccache : %s\n", LogTime(), PROGRAM, error_message(code));
113 retval = 1;
114 goto cleanup;
115 }
116 }
117 /*
118 * getting default principal from cache
119 */
120
121 code = krb5_cc_get_principal(kparam.context, kparam.cc[ccindex], &principal);
122 if (code) {
123 if (principal)
124 krb5_free_principal(kparam.context, principal);
125 principal = NULL;
126 debug((char *) "%s| %s: DEBUG: No default principal found in ccache : %s\n", LogTime(), PROGRAM, error_message(code));
127
128 } else {
129 /*
130 * Look for krbtgt and check if it is expired (or soon to be expired)
131 */
132 code = krb5_cc_start_seq_get(kparam.context, kparam.cc[ccindex], &ccursor);
133 if (code) {
134 error((char *) "%s| %s: ERROR: Error while starting ccache scan : %s\n", LogTime(), PROGRAM, error_message(code));
135 code = krb5_cc_close (kparam.context, kparam.cc[ccindex]);
136 if (code) {
137 error((char *) "%s| %s: ERROR: while closing ccache : %s\n", LogTime(), PROGRAM, error_message(code));
138 }
139 if (kparam.cc[ccindex]) {
140 code = krb5_cc_destroy(kparam.context, kparam.cc[ccindex]);
141 if (code) {
142 error((char *) "%s| %s: ERROR: while destroying ccache : %s\n", LogTime(), PROGRAM, error_message(code));
143 }
144 }
145 } else {
146 krb5_error_code code2 = 0;
147 creds = (krb5_creds *) xcalloc(1,sizeof(*creds));
148 while ((krb5_cc_next_cred(kparam.context, kparam.cc[ccindex], &ccursor, creds)) == 0) {
149 code2 = krb5_unparse_name(kparam.context, creds->server, &principal_name);
150 if (code2) {
151 error((char *) "%s| %s: ERROR: Error while unparsing principal : %s\n", LogTime(), PROGRAM, error_message(code2));
152 code = krb5_cc_destroy(kparam.context, kparam.cc[ccindex]);
153 if (code) {
154 error((char *) "%s| %s: ERROR: while destroying ccache : %s\n", LogTime(), PROGRAM, error_message(code));
155 }
156 if (creds)
157 krb5_free_creds(kparam.context, creds);
158 creds = NULL;
159 safe_free(principal_name);
160 debug((char *) "%s| %s: DEBUG: Reset credential cache to %s\n", LogTime(), PROGRAM, mem_cache);
161 code = krb5_cc_resolve(kparam.context, mem_cache, &kparam.cc[ccindex]);
162 if (code) {
163 error((char *) "%s| %s: ERROR: Error while resolving memory ccache : %s\n", LogTime(), PROGRAM, error_message(code));
164 retval = 1;
165 goto cleanup;
166 }
167 code =1;
168 break;
169 }
170 if (!strncmp(KRB5_TGS_NAME,principal_name,KRB5_TGS_NAME_SIZE)) {
171 time_t now;
172 static krb5_deltat skew=MAX_SKEW;
173
174 debug((char *) "%s| %s: DEBUG: Found %s in cache : %s\n", LogTime(), PROGRAM,KRB5_TGS_NAME,principal_name);
175 /*
176 * Check time
177 */
178 time(&now);
179 debug((char *) "%s| %s: DEBUG: credential time diff %d\n", LogTime(), PROGRAM, (int)(creds->times.endtime - now));
180 if (creds->times.endtime - now < 2*skew) {
181 debug((char *) "%s| %s: DEBUG: credential will soon expire %d\n", LogTime(), PROGRAM, (int)(creds->times.endtime - now));
182 if (principal)
183 krb5_free_principal(kparam.context, principal);
184 principal = NULL;
185 code = krb5_cc_destroy(kparam.context, kparam.cc[ccindex]);
186 if (code) {
187 error((char *) "%s| %s: ERROR: while destroying ccache : %s\n", LogTime(), PROGRAM, error_message(code));
188 }
189 if (creds)
190 krb5_free_creds(kparam.context, creds);
191 creds = NULL;
192 safe_free(principal_name);
193 debug((char *) "%s| %s: DEBUG: Reset credential cache to %s\n", LogTime(), PROGRAM, mem_cache);
194 code = krb5_cc_resolve(kparam.context, mem_cache, &kparam.cc[ccindex]);
195 if (code) {
196 error((char *) "%s| %s: ERROR: Error while resolving memory ccache : %s\n", LogTime(), PROGRAM, error_message(code));
197 retval = 1;
198 goto cleanup;
199 }
200 code = 1;
201 } else {
202 safe_free(principal_name);
203 }
204 break;
205 }
206 if (creds)
207 krb5_free_creds(kparam.context, creds);
208 creds = (krb5_creds *) xcalloc(1,sizeof(*creds));
209 safe_free(principal_name);
210 }
211 if (creds)
212 krb5_free_creds(kparam.context, creds);
213 creds = NULL;
214 code2 = krb5_cc_end_seq_get(kparam.context, kparam.cc[ccindex], &ccursor);
215 if (code2) {
216 error((char *) "%s| %s: ERROR: Error while ending ccache scan : %s\n", LogTime(), PROGRAM, error_message(code));
217 retval = 1;
218 goto cleanup;
219 }
220 }
221 }
222 if (code) {
223 /*
224 * getting default keytab name
225 */
226
227 debug((char *) "%s| %s: DEBUG: Get default keytab file name\n", LogTime(), PROGRAM);
228 krb5_kt_default_name(kparam.context, buf, KT_PATH_MAX);
229 p = strchr(buf, ':'); /* Find the end if "FILE:" */
230 if (p)
231 ++p; /* step past : */
232 keytab_name = xstrdup(p ? p : buf);
233 debug((char *) "%s| %s: DEBUG: Got default keytab file name %s\n", LogTime(), PROGRAM, keytab_name);
234
235 code = krb5_kt_resolve(kparam.context, keytab_name, &keytab);
236 if (code) {
237 error((char *) "%s| %s: ERROR: Error while resolving keytab %s : %s\n", LogTime(), PROGRAM, keytab_name, error_message(code));
238 retval = 1;
239 goto cleanup;
240 }
241 code = krb5_kt_start_seq_get(kparam.context, keytab, &cursor);
242 if (code) {
243 error((char *) "%s| %s: ERROR: Error while starting keytab scan : %s\n", LogTime(), PROGRAM, error_message(code));
244 retval = 1;
245 goto cleanup;
246 }
247 debug((char *) "%s| %s: DEBUG: Get principal name from keytab %s\n", LogTime(), PROGRAM, keytab_name);
248
249 nprinc = 0;
250 while ((code = krb5_kt_next_entry(kparam.context, keytab, &entry, &cursor)) == 0) {
251 int found = 0;
252
253 principal_list = (krb5_principal *) xrealloc(principal_list, sizeof(krb5_principal) * (nprinc + 1));
254 krb5_copy_principal(kparam.context, entry.principal, &principal_list[nprinc++]);
255 #if USE_HEIMDAL_KRB5
256 debug((char *) "%s| %s: DEBUG: Keytab entry has realm name: %s\n", LogTime(), PROGRAM, entry.principal->realm);
257 #else
258 debug((char *) "%s| %s: DEBUG: Keytab entry has realm name: %s\n", LogTime(), PROGRAM, krb5_princ_realm(kparam.context, entry.principal)->data);
259 #endif
260 #if USE_HEIMDAL_KRB5
261 if (!strcasecmp(domain, entry.principal->realm))
262 #else
263 if (!strcasecmp(domain, krb5_princ_realm(kparam.context, entry.principal)->data))
264 #endif
265 {
266 code = krb5_unparse_name(kparam.context, entry.principal, &principal_name);
267 if (code) {
268 error((char *) "%s| %s: ERROR: Error while unparsing principal name : %s\n", LogTime(), PROGRAM, error_message(code));
269 } else {
270 debug((char *) "%s| %s: DEBUG: Found principal name: %s\n", LogTime(), PROGRAM, principal_name);
271 found = 1;
272 }
273 }
274 #if USE_HEIMDAL_KRB5 || ( HAVE_KRB5_KT_FREE_ENTRY && HAVE_DECL_KRB5_KT_FREE_ENTRY )
275 code = krb5_kt_free_entry(kparam.context, &entry);
276 #else
277 code = krb5_free_keytab_entry_contents(kparam.context, &entry);
278 #endif
279 if (code) {
280 error((char *) "%s| %s: ERROR: Error while freeing keytab entry : %s\n", LogTime(), PROGRAM, error_message(code));
281 retval = 1;
282 break;
283 }
284 if (found) {
285 debug((char *) "%s| %s: DEBUG: Got principal name %s\n", LogTime(), PROGRAM, principal_name);
286 /*
287 * build principal
288 */
289 code = krb5_parse_name(kparam.context, principal_name, &principal);
290 if (code) {
291 error((char *) "%s| %s: ERROR: Error while parsing name %s : %s\n", LogTime(), PROGRAM, principal_name, error_message(code));
292 safe_free(principal_name);
293 if (principal)
294 krb5_free_principal(kparam.context, principal);
295 found = 0;
296 continue;
297 }
298 creds = (krb5_creds *) xcalloc(1,sizeof(*creds));
299
300 /*
301 * get credentials
302 */
303 #if HAVE_GET_INIT_CREDS_KEYTAB
304 code = krb5_get_init_creds_keytab(kparam.context, creds, principal, keytab, 0, NULL, NULL);
305 #else
306 service = (char *) xmalloc(strlen("krbtgt") + 2 * strlen(domain) + 3);
307 snprintf(service, strlen("krbtgt") + 2 * strlen(domain) + 3, "krbtgt/%s@%s", domain, domain);
308 creds->client = principal;
309 code = krb5_parse_name(kparam.context, service, &creds->server);
310 xfree(service);
311 code = krb5_get_in_tkt_with_keytab(kparam.context, 0, NULL, NULL, NULL, keytab, NULL, creds, 0);
312 #endif
313
314 if (code) {
315 error((char *) "%s| %s: ERROR: Error while initialising credentials from keytab : %s\n", LogTime(), PROGRAM, error_message(code));
316 safe_free(principal_name);
317 if (principal)
318 krb5_free_principal(kparam.context, principal);
319 if (creds)
320 krb5_free_creds(kparam.context, creds);
321 creds = NULL;
322 found = 0;
323 continue;
324 }
325 code = krb5_cc_initialize(kparam.context, kparam.cc[ccindex], principal);
326 if (code) {
327 error((char *) "%s| %s: ERROR: Error while initializing memory caches : %s\n", LogTime(), PROGRAM, error_message(code));
328 safe_free(principal_name);
329 if (principal)
330 krb5_free_principal(kparam.context, principal);
331 if (creds)
332 krb5_free_creds(kparam.context, creds);
333 creds = NULL;
334 found = 0;
335 continue;
336 }
337 code = krb5_cc_store_cred(kparam.context, kparam.cc[ccindex], creds);
338 if (code) {
339 error((char *) "%s| %s: ERROR: Error while storing credentials : %s\n", LogTime(), PROGRAM, error_message(code));
340 if (principal)
341 krb5_free_principal(kparam.context, principal);
342 safe_free(principal_name);
343 if (creds)
344 krb5_free_creds(kparam.context, creds);
345 creds = NULL;
346 found = 0;
347 continue;
348 }
349 debug((char *) "%s| %s: DEBUG: Stored credentials\n", LogTime(), PROGRAM);
350 break;
351 }
352 }
353
354 if (code && code != KRB5_KT_END) {
355 error((char *) "%s| %s: ERROR: Error while scanning keytab : %s\n", LogTime(), PROGRAM, error_message(code));
356 retval = 1;
357 goto cleanup;
358 }
359 code = krb5_kt_end_seq_get(kparam.context, keytab, &cursor);
360 if (code) {
361 error((char *) "%s| %s: ERROR: Error while ending keytab scan : %s\n", LogTime(), PROGRAM, error_message(code));
362 retval = 1;
363 goto cleanup;
364 }
365
366 /*
367 * if no principal name found in keytab for domain use the prinipal name which can get a TGT
368 */
369 if (!principal_name) {
370 size_t i;
371 debug((char *) "%s| %s: DEBUG: Did not find a principal in keytab for domain %s.\n", LogTime(), PROGRAM, domain);
372 debug((char *) "%s| %s: DEBUG: Try to get principal of trusted domain.\n", LogTime(), PROGRAM);
373
374 for (i = 0; i < nprinc; ++i) {
375 krb5_creds *tgt_creds = NULL;
376 creds = (krb5_creds *) xmalloc(sizeof(*creds));
377 memset(creds, 0, sizeof(*creds));
378 /*
379 * get credentials
380 */
381 code = krb5_unparse_name(kparam.context, principal_list[i], &principal_name);
382 if (code) {
383 debug((char *) "%s| %s: DEBUG: Error while unparsing principal name : %s\n", LogTime(), PROGRAM, error_message(code));
384 goto loop_end;
385 }
386 debug((char *) "%s| %s: DEBUG: Keytab entry has principal: %s\n", LogTime(), PROGRAM, principal_name);
387
388 #if HAVE_GET_INIT_CREDS_KEYTAB
389 code = krb5_get_init_creds_keytab(kparam.context, creds, principal_list[i], keytab, 0, NULL, NULL);
390 #else
391 service = (char *) xmalloc(strlen("krbtgt") + 2 * strlen(domain) + 3);
392 snprintf(service, strlen("krbtgt") + 2 * strlen(domain) + 3, "krbtgt/%s@%s", domain, domain);
393 creds->client = principal_list[i];
394 code = krb5_parse_name(kparam.context, service, &creds->server);
395 xfree(service);
396 code = krb5_get_in_tkt_with_keytab(kparam.context, 0, NULL, NULL, NULL, keytab, NULL, creds, 0);
397 #endif
398 if (code) {
399 debug((char *) "%s| %s: DEBUG: Error while initialising credentials from keytab : %s\n", LogTime(), PROGRAM, error_message(code));
400 goto loop_end;
401 }
402 code = krb5_cc_initialize(kparam.context, kparam.cc[ccindex], principal_list[i]);
403 if (code) {
404 error((char *) "%s| %s: ERROR: Error while initializing memory caches : %s\n", LogTime(), PROGRAM, error_message(code));
405 goto loop_end;
406 }
407 code = krb5_cc_store_cred(kparam.context, kparam.cc[ccindex], creds);
408 if (code) {
409 debug((char *) "%s| %s: DEBUG: Error while storing credentials : %s\n", LogTime(), PROGRAM, error_message(code));
410 goto loop_end;
411 }
412 if (creds->server)
413 krb5_free_principal(kparam.context, creds->server);
414 #if USE_HEIMDAL_KRB5
415 service = (char *) xmalloc(strlen("krbtgt") + strlen(domain) + strlen(principal_list[i]->realm) + 3);
416 snprintf(service, strlen("krbtgt") + strlen(domain) + strlen(principal_list[i]->realm) + 3, "krbtgt/%s@%s", domain, principal_list[i]->realm);
417 #else
418 service = (char *) xmalloc(strlen("krbtgt") + strlen(domain) + strlen(krb5_princ_realm(kparam.context, principal_list[i])->data) + 3);
419 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);
420 #endif
421 code = krb5_parse_name(kparam.context, service, &creds->server);
422 xfree(service);
423 if (code) {
424 error((char *) "%s| %s: ERROR: Error while initialising TGT credentials : %s\n", LogTime(), PROGRAM, error_message(code));
425 goto loop_end;
426 }
427 code = krb5_get_credentials(kparam.context, 0, kparam.cc[ccindex], creds, &tgt_creds);
428 if (code) {
429 debug((char *) "%s| %s: DEBUG: Error while getting tgt : %s\n", LogTime(), PROGRAM, error_message(code));
430 goto loop_end;
431 } else {
432 debug((char *) "%s| %s: DEBUG: Found trusted principal name: %s\n", LogTime(), PROGRAM, principal_name);
433 if (tgt_creds)
434 krb5_free_creds(kparam.context, tgt_creds);
435 tgt_creds = NULL;
436 break;
437 }
438
439 loop_end:
440 safe_free(principal_name);
441 if (tgt_creds)
442 krb5_free_creds(kparam.context, tgt_creds);
443 tgt_creds = NULL;
444 if (creds)
445 krb5_free_creds(kparam.context, creds);
446 creds = NULL;
447
448 }
449
450 if (creds)
451 krb5_free_creds(kparam.context, creds);
452 creds = NULL;
453 }
454 } else {
455 debug((char *) "%s| %s: DEBUG: Got principal from ccache\n", LogTime(), PROGRAM);
456 /*
457 * get credentials
458 */
459 code = krb5_unparse_name(kparam.context, principal, &principal_name);
460 if (code) {
461 debug((char *) "%s| %s: DEBUG: Error while unparsing principal name : %s\n", LogTime(), PROGRAM, error_message(code));
462 retval = 1;
463 goto cleanup;
464 }
465 debug((char *) "%s| %s: DEBUG: ccache has principal: %s\n", LogTime(), PROGRAM, principal_name);
466 }
467
468 if (!principal_name) {
469 debug((char *) "%s| %s: DEBUG: Got no principal name\n", LogTime(), PROGRAM);
470 retval = 1;
471 }
472 cleanup:
473 if (keytab)
474 krb5_kt_close(kparam.context, keytab);
475 xfree(keytab_name);
476 xfree(principal_name);
477 xfree(mem_cache);
478 if (principal)
479 krb5_free_principal(kparam.context, principal);
480 for (j = 0; j < nprinc; ++j) {
481 if (principal_list[j])
482 krb5_free_principal(kparam.context, principal_list[j]);
483 }
484 xfree(principal_list);
485 if (creds)
486 krb5_free_creds(kparam.context, creds);
487 return (retval);
488 }
489 #endif
490