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