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