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