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