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