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

 

Introduction

Documentation

Support

Miscellaneous