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