1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
2 /*
3  * soup-auth-negotiate.c: HTTP Negotiate Authentication helper
4  *
5  * Copyright (C) 2009,2013 Guido Guenther <agx@sigxcpu.org>
6  * Copyright (C) 2016 Red Hat, Inc.
7  */
8 
9 #ifdef HAVE_CONFIG_H
10 #include <config.h>
11 #endif
12 
13 #include <string.h>
14 
15 #ifdef LIBSOUP_HAVE_GSSAPI
16 #include <gssapi/gssapi.h>
17 #endif /* LIBSOUP_HAVE_GSSAPI */
18 
19 #include "soup-auth-negotiate.h"
20 #include "soup-headers.h"
21 #include "soup-message.h"
22 #include "soup-message-private.h"
23 #include "soup-misc-private.h"
24 #include "soup-uri.h"
25 
26 /**
27  * soup_auth_negotiate_supported:
28  *
29  * Indicates whether libsoup was built with GSSAPI support. If this is
30  * %FALSE, %SOUP_TYPE_AUTH_NEGOTIATE will still be defined and can
31  * still be added to a #SoupSession, but libsoup will never attempt to
32  * actually use this auth type.
33  *
34  * Since: 2.54
35  */
36 gboolean
soup_auth_negotiate_supported(void)37 soup_auth_negotiate_supported (void)
38 {
39 #ifdef LIBSOUP_HAVE_GSSAPI
40 	return TRUE;
41 #else
42 	return FALSE;
43 #endif
44 }
45 
46 #define AUTH_GSS_ERROR      -1
47 #define AUTH_GSS_COMPLETE    1
48 #define AUTH_GSS_CONTINUE    0
49 
50 typedef enum {
51 	SOUP_NEGOTIATE_NEW,
52 	SOUP_NEGOTIATE_RECEIVED_CHALLENGE, /* received initial negotiate header */
53 	SOUP_NEGOTIATE_SENT_RESPONSE,      /* sent response to server */
54 	SOUP_NEGOTIATE_FAILED
55 } SoupNegotiateState;
56 
57 typedef struct {
58 	gboolean initialized;
59 	gchar *response_header;
60 
61 #ifdef LIBSOUP_HAVE_GSSAPI
62 	gss_ctx_id_t context;
63 	gss_name_t   server_name;
64 #endif
65 
66 	SoupNegotiateState state;
67 } SoupNegotiateConnectionState;
68 
69 typedef struct {
70 	gboolean is_authenticated;
71 } SoupAuthNegotiatePrivate;
72 
73 /**
74  * SOUP_TYPE_AUTH_NEGOTIATE:
75  *
76  * A #GType corresponding to HTTP-based GSS-Negotiate authentication.
77  * #SoupSessions do not support this type by default; if you want to
78  * enable support for it, call soup_session_add_feature_by_type(),
79  * passing %SOUP_TYPE_AUTH_NEGOTIATE.
80  *
81  * This auth type will only work if libsoup was compiled with GSSAPI
82  * support; you can check soup_auth_negotiate_supported() to see if it
83  * was.
84  *
85  * Since: 2.54
86  */
87 G_DEFINE_TYPE_WITH_PRIVATE (SoupAuthNegotiate, soup_auth_negotiate, SOUP_TYPE_CONNECTION_AUTH)
88 
89 #ifdef LIBSOUP_HAVE_GSSAPI
90 static gboolean check_auth_trusted_uri (SoupConnectionAuth *auth,
91 					SoupMessage *msg);
92 static gboolean soup_gss_build_response (SoupNegotiateConnectionState *conn,
93 					 SoupAuth *auth, GError **err);
94 static void soup_gss_client_cleanup (SoupNegotiateConnectionState *conn);
95 static gboolean soup_gss_client_init (SoupNegotiateConnectionState *conn,
96 				      const char *host, GError **err);
97 static int soup_gss_client_step (SoupNegotiateConnectionState *conn,
98 				 const char *host, GError **err);
99 
100 static GSList *trusted_uris;
101 static GSList *blacklisted_uris;
102 
103 static void parse_uris_from_env_variable (const gchar *env_variable, GSList **list);
104 
105 static void check_server_response (SoupMessage *msg, gpointer auth);
106 
107 static const char spnego_OID[] = "\x2b\x06\x01\x05\x05\x02";
108 static const gss_OID_desc gss_mech_spnego = { sizeof (spnego_OID) - 1, (void *) &spnego_OID };
109 
110 static gpointer
soup_auth_negotiate_create_connection_state(SoupConnectionAuth * auth)111 soup_auth_negotiate_create_connection_state (SoupConnectionAuth *auth)
112 {
113 	SoupNegotiateConnectionState *conn;
114 
115 	conn = g_slice_new0 (SoupNegotiateConnectionState);
116 	conn->state = SOUP_NEGOTIATE_NEW;
117 
118 	return conn;
119 }
120 
121 static void
free_connection_state_data(SoupNegotiateConnectionState * conn)122 free_connection_state_data (SoupNegotiateConnectionState *conn)
123 {
124 	soup_gss_client_cleanup (conn);
125 	g_free (conn->response_header);
126 }
127 
128 static void
soup_auth_negotiate_free_connection_state(SoupConnectionAuth * auth,gpointer state)129 soup_auth_negotiate_free_connection_state (SoupConnectionAuth *auth,
130 					   gpointer state)
131 {
132 	SoupNegotiateConnectionState *conn = state;
133 
134 	free_connection_state_data (conn);
135 
136 	g_slice_free (SoupNegotiateConnectionState, conn);
137 }
138 
139 static GSList *
soup_auth_negotiate_get_protection_space(SoupAuth * auth,SoupURI * source_uri)140 soup_auth_negotiate_get_protection_space (SoupAuth *auth, SoupURI *source_uri)
141 {
142 	char *space, *p;
143 
144 	space = g_strdup (source_uri->path);
145 
146 	/* Strip filename component */
147 	p = strrchr (space, '/');
148 	if (p && p == space && p[1])
149 		p[1] = '\0';
150 	else if (p && p[1])
151 		*p = '\0';
152 
153 	return g_slist_prepend (NULL, space);
154 }
155 
156 static void
soup_auth_negotiate_authenticate(SoupAuth * auth,const char * username,const char * password)157 soup_auth_negotiate_authenticate (SoupAuth *auth, const char *username,
158 				  const char *password)
159 {
160 	SoupAuthNegotiate *negotiate = SOUP_AUTH_NEGOTIATE (auth);
161 	SoupAuthNegotiatePrivate *priv = soup_auth_negotiate_get_instance_private (negotiate);
162 
163 	/* It is not possible to authenticate with username and password. */
164 	priv->is_authenticated = FALSE;
165 }
166 
167 static gboolean
soup_auth_negotiate_is_authenticated(SoupAuth * auth)168 soup_auth_negotiate_is_authenticated (SoupAuth *auth)
169 {
170 	SoupAuthNegotiate *negotiate = SOUP_AUTH_NEGOTIATE (auth);
171 	SoupAuthNegotiatePrivate *priv = soup_auth_negotiate_get_instance_private (negotiate);
172 
173 	/* We are authenticated just in case we received the GSS_S_COMPLETE. */
174 	return priv->is_authenticated;
175 }
176 
177 static gboolean
soup_auth_negotiate_can_authenticate(SoupAuth * auth)178 soup_auth_negotiate_can_authenticate (SoupAuth *auth)
179 {
180 	return FALSE;
181 }
182 
183 static char *
soup_auth_negotiate_get_connection_authorization(SoupConnectionAuth * auth,SoupMessage * msg,gpointer state)184 soup_auth_negotiate_get_connection_authorization (SoupConnectionAuth *auth,
185 						  SoupMessage *msg,
186 						  gpointer state)
187 {
188 	SoupNegotiateConnectionState *conn = state;
189 	char *header = NULL;
190 
191 	if (conn->state == SOUP_NEGOTIATE_NEW) {
192 		GError *err = NULL;
193 
194 		if (!check_auth_trusted_uri (auth, msg)) {
195 			conn->state = SOUP_NEGOTIATE_FAILED;
196 			return NULL;
197 		}
198 
199 		if (!soup_gss_build_response (conn, SOUP_AUTH (auth), &err)) {
200 			/* FIXME: report further upward via
201 			 * soup_message_get_error_message  */
202 			if (conn->initialized)
203 				g_warning ("gssapi step failed: %s", err->message);
204 			else
205 				g_warning ("gssapi init failed: %s", err->message);
206 			conn->state = SOUP_NEGOTIATE_FAILED;
207 			g_clear_error (&err);
208 
209 			return NULL;
210 		}
211 	}
212 
213 	if (conn->response_header) {
214 		header = conn->response_header;
215 		conn->response_header = NULL;
216 		conn->state = SOUP_NEGOTIATE_SENT_RESPONSE;
217 	}
218 
219 	return header;
220 }
221 
222 static gboolean
soup_auth_negotiate_is_connection_ready(SoupConnectionAuth * auth,SoupMessage * msg,gpointer state)223 soup_auth_negotiate_is_connection_ready (SoupConnectionAuth *auth,
224 					 SoupMessage        *msg,
225 					 gpointer            state)
226 {
227 	SoupNegotiateConnectionState *conn = state;
228 
229 	return conn->state != SOUP_NEGOTIATE_FAILED;
230 }
231 #endif /* LIBSOUP_HAVE_GSSAPI */
232 
233 static gboolean
soup_auth_negotiate_update_connection(SoupConnectionAuth * auth,SoupMessage * msg,const char * header,gpointer state)234 soup_auth_negotiate_update_connection (SoupConnectionAuth *auth, SoupMessage *msg,
235 				       const char *header, gpointer state)
236 {
237 #ifdef LIBSOUP_HAVE_GSSAPI
238 	gboolean success = TRUE;
239 	SoupNegotiateConnectionState *conn = state;
240 	GError *err = NULL;
241 
242 	if (!check_auth_trusted_uri (auth, msg)) {
243 		conn->state = SOUP_NEGOTIATE_FAILED;
244 		goto out;
245 	}
246 
247 	/* Found negotiate header with no token, start negotiate */
248 	if (strcmp (header, "Negotiate") == 0) {
249 		/* If we were already negotiating and we get a 401
250 		 * with no token, start again. */
251 		if (conn->state == SOUP_NEGOTIATE_SENT_RESPONSE) {
252 			free_connection_state_data (conn);
253 			conn->initialized = FALSE;
254 		}
255 
256 		conn->state = SOUP_NEGOTIATE_RECEIVED_CHALLENGE;
257 		if (soup_gss_build_response (conn, SOUP_AUTH (auth), &err)) {
258 			/* Connect the signal only once per message */
259 			if (!g_object_get_data (G_OBJECT (msg), "negotiate-got-headers-connected")) {
260 				/* Wait for the 2xx response to verify server response */
261 				g_signal_connect_data (msg,
262 						       "got_headers",
263 						       G_CALLBACK (check_server_response),
264 						       g_object_ref (auth),
265 						       (GClosureNotify) g_object_unref,
266 						       0);
267 				/* Mark that the signal was connected */
268 				g_object_set_data (G_OBJECT (msg),
269 						   "negotiate-got-headers-connected",
270 						   GINT_TO_POINTER (1));
271 			}
272 			goto out;
273 		} else {
274 			/* FIXME: report further upward via
275 			 * soup_message_get_error_message  */
276 			if (conn->initialized)
277 				g_warning ("gssapi step failed: %s", err->message);
278 			else
279 				g_warning ("gssapi init failed: %s", err->message);
280 			success = FALSE;
281 		}
282 	} else if (!strncmp (header, "Negotiate ", 10)) {
283 		if (soup_gss_client_step (conn, header + 10, &err) == AUTH_GSS_CONTINUE) {
284 			conn->state = SOUP_NEGOTIATE_RECEIVED_CHALLENGE;
285 			goto out;
286 		}
287 	}
288 
289 	conn->state = SOUP_NEGOTIATE_FAILED;
290  out:
291 	g_clear_error (&err);
292 	return success;
293 #else
294 	return FALSE;
295 #endif /* LIBSOUP_HAVE_GSSAPI */
296 }
297 
298 static void
soup_auth_negotiate_init(SoupAuthNegotiate * negotiate)299 soup_auth_negotiate_init (SoupAuthNegotiate *negotiate)
300 {
301 	g_object_set (G_OBJECT (negotiate), SOUP_AUTH_REALM, "", NULL);
302 }
303 
304 static void
soup_auth_negotiate_class_init(SoupAuthNegotiateClass * auth_negotiate_class)305 soup_auth_negotiate_class_init (SoupAuthNegotiateClass *auth_negotiate_class)
306 {
307 	SoupAuthClass *auth_class = SOUP_AUTH_CLASS (auth_negotiate_class);
308 	SoupConnectionAuthClass *conn_auth_class =
309 			SOUP_CONNECTION_AUTH_CLASS (auth_negotiate_class);
310 
311 	auth_class->scheme_name = "Negotiate";
312 	auth_class->strength = 0;
313 
314 	conn_auth_class->update_connection = soup_auth_negotiate_update_connection;
315 #ifdef LIBSOUP_HAVE_GSSAPI
316 	auth_class->strength = 7;
317 
318 	conn_auth_class->create_connection_state = soup_auth_negotiate_create_connection_state;
319 	conn_auth_class->free_connection_state = soup_auth_negotiate_free_connection_state;
320 	conn_auth_class->get_connection_authorization = soup_auth_negotiate_get_connection_authorization;
321 	conn_auth_class->is_connection_ready = soup_auth_negotiate_is_connection_ready;
322 
323 	auth_class->get_protection_space = soup_auth_negotiate_get_protection_space;
324 	auth_class->authenticate = soup_auth_negotiate_authenticate;
325 	auth_class->is_authenticated = soup_auth_negotiate_is_authenticated;
326 	auth_class->can_authenticate = soup_auth_negotiate_can_authenticate;
327 
328 	parse_uris_from_env_variable ("SOUP_GSSAPI_TRUSTED_URIS", &trusted_uris);
329 	parse_uris_from_env_variable ("SOUP_GSSAPI_BLACKLISTED_URIS", &blacklisted_uris);
330 #endif /* LIBSOUP_HAVE_GSSAPI */
331 }
332 
333 #ifdef LIBSOUP_HAVE_GSSAPI
334 static void
check_server_response(SoupMessage * msg,gpointer auth)335 check_server_response (SoupMessage *msg, gpointer auth)
336 {
337 	gint ret;
338 	const char *auth_headers;
339 	GError *err = NULL;
340 	SoupAuthNegotiate *negotiate = auth;
341 	SoupAuthNegotiatePrivate *priv = soup_auth_negotiate_get_instance_private (negotiate);
342 	SoupNegotiateConnectionState *conn;
343 
344 	conn = soup_connection_auth_get_connection_state_for_message (SOUP_CONNECTION_AUTH (auth), msg);
345 	if (!conn)
346 		return;
347 
348 	if (auth != soup_message_get_auth (msg))
349 		return;
350 
351 	if (msg->status_code == SOUP_STATUS_UNAUTHORIZED)
352 		return;
353 
354 	/* FIXME: need to check for proxy-auth too */
355 	auth_headers = soup_message_headers_get_one (msg->response_headers,
356 						     "WWW-Authenticate");
357 	if (!auth_headers || g_ascii_strncasecmp (auth_headers, "Negotiate ", 10) != 0) {
358 		g_warning ("Failed to parse auth header");
359 		conn->state = SOUP_NEGOTIATE_FAILED;
360 		goto out;
361 	}
362 
363 	ret = soup_gss_client_step (conn, auth_headers + 10, &err);
364 
365 	switch (ret) {
366 	case AUTH_GSS_COMPLETE:
367 		priv->is_authenticated = TRUE;
368 		break;
369 	case AUTH_GSS_CONTINUE:
370 		conn->state = SOUP_NEGOTIATE_RECEIVED_CHALLENGE;
371 		break;
372 	case AUTH_GSS_ERROR:
373 		if (err)
374 			g_warning ("%s", err->message);
375 		/* Unfortunately, so many programs (curl, Firefox, ..) ignore
376 		 * the return token that is included in the response, so it is
377 		 * possible that there are servers that send back broken stuff.
378 		 * Try to behave in the right way (pass the token to
379 		 * gss_init_sec_context()), show a warning, but don't fail
380 		 * if the server returned 200. */
381 		if (msg->status_code == SOUP_STATUS_OK)
382 			priv->is_authenticated = TRUE;
383 		else
384 			conn->state = SOUP_NEGOTIATE_FAILED;
385 		break;
386 	default:
387 		conn->state = SOUP_NEGOTIATE_FAILED;
388 	}
389  out:
390 	g_clear_error (&err);
391 }
392 
393 /* Check if scheme://host:port from message matches the given URI. */
394 static gint
match_base_uri(SoupURI * list_uri,SoupURI * msg_uri)395 match_base_uri (SoupURI *list_uri, SoupURI *msg_uri)
396 {
397 	if (msg_uri->scheme != list_uri->scheme)
398 		return 1;
399 
400 	if (list_uri->port && (msg_uri->port != list_uri->port))
401 		return 1;
402 
403 	if (list_uri->host)
404 		return !soup_host_matches_host (msg_uri->host, list_uri->host);
405 
406 	return 0;
407 }
408 
409 /* Parses a comma separated list of URIS from the environment. */
410 static void
parse_uris_from_env_variable(const gchar * env_variable,GSList ** list)411 parse_uris_from_env_variable (const gchar *env_variable, GSList **list)
412 {
413 	gchar **uris = NULL;
414 	const gchar *env;
415 	gint i;
416 	guint length;
417 
418 	/* Initialize the list */
419 	*list = NULL;
420 
421 	if (!(env = g_getenv (env_variable)))
422 		return;
423 
424 	if (!(uris = g_strsplit (env, ",", -1)))
425 		return;
426 
427 	length = g_strv_length (uris);
428 	for (i = 0; i < length; i++) {
429 		SoupURI *uri;
430 
431 		/* If the supplied URI is valid, append it to the list */
432 		if ((uri = soup_uri_new (uris[i])))
433 			*list = g_slist_prepend (*list, uri);
434 	}
435 
436 	g_strfreev (uris);
437 }
438 
439 static gboolean
check_auth_trusted_uri(SoupConnectionAuth * auth,SoupMessage * msg)440 check_auth_trusted_uri (SoupConnectionAuth *auth, SoupMessage *msg)
441 {
442 	SoupURI *msg_uri;
443 	GSList *matched = NULL;
444 
445 	g_return_val_if_fail (auth != NULL, FALSE);
446 	g_return_val_if_fail (msg != NULL, FALSE);
447 
448 	msg_uri = soup_message_get_uri (msg);
449 
450 	/* First check if the URI is not on blacklist */
451 	if (blacklisted_uris &&
452 	    g_slist_find_custom (blacklisted_uris, msg_uri, (GCompareFunc) match_base_uri))
453 		return FALSE;
454 
455 	/* If no trusted URIs are set, we allow all HTTPS URIs */
456 	if (!trusted_uris)
457 		return soup_uri_is_https (msg_uri, NULL);
458 
459 	matched = g_slist_find_custom (trusted_uris,
460 				       msg_uri,
461 				       (GCompareFunc) match_base_uri);
462 
463 	return matched ? TRUE : FALSE;
464 }
465 
466 static gboolean
soup_gss_build_response(SoupNegotiateConnectionState * conn,SoupAuth * auth,GError ** err)467 soup_gss_build_response (SoupNegotiateConnectionState *conn, SoupAuth *auth, GError **err)
468 {
469 	if (!conn->initialized)
470 		if (!soup_gss_client_init (conn, soup_auth_get_host (auth), err))
471 			return FALSE;
472 
473 	if (soup_gss_client_step (conn, "", err) != AUTH_GSS_CONTINUE)
474 		return FALSE;
475 
476 	return TRUE;
477 }
478 
479 static void
soup_gss_error(OM_uint32 err_maj,OM_uint32 err_min,GError ** err)480 soup_gss_error (OM_uint32 err_maj, OM_uint32 err_min, GError **err)
481 {
482 	OM_uint32 maj_stat, min_stat, msg_ctx = 0;
483 	gss_buffer_desc status;
484 	gchar *buf_maj = NULL, *buf_min = NULL;
485 
486 	do {
487 		maj_stat = gss_display_status (&min_stat,
488 					       err_maj,
489 					       GSS_C_GSS_CODE,
490 					       (gss_OID) &gss_mech_spnego,
491 					       &msg_ctx,
492 					       &status);
493 		if (GSS_ERROR (maj_stat))
494 			break;
495 
496 		buf_maj = g_strdup ((gchar *) status.value);
497 		gss_release_buffer (&min_stat, &status);
498 
499 		maj_stat = gss_display_status (&min_stat,
500 					       err_min,
501 					       GSS_C_MECH_CODE,
502 					       GSS_C_NULL_OID,
503 					       &msg_ctx,
504 					       &status);
505 		if (!GSS_ERROR (maj_stat)) {
506 			buf_min = g_strdup ((gchar *) status.value);
507 			gss_release_buffer (&min_stat, &status);
508 		}
509 
510 		if (err && *err == NULL) {
511 			g_set_error (err,
512 				     SOUP_HTTP_ERROR,
513 				     SOUP_STATUS_UNAUTHORIZED,
514 				     "%s: %s",
515 				     buf_maj,
516 				     buf_min ? buf_min : "");
517 		}
518 		g_free (buf_maj);
519 		g_free (buf_min);
520 		buf_min = buf_maj = NULL;
521 	} while (!GSS_ERROR (maj_stat) && msg_ctx != 0);
522 }
523 
524 static gboolean
soup_gss_client_init(SoupNegotiateConnectionState * conn,const gchar * host,GError ** err)525 soup_gss_client_init (SoupNegotiateConnectionState *conn, const gchar *host, GError **err)
526 {
527 	OM_uint32 maj_stat, min_stat;
528 	gchar *service = NULL;
529 	gss_buffer_desc token = GSS_C_EMPTY_BUFFER;
530 	gboolean ret = FALSE;
531 	gchar *h;
532 
533 	conn->server_name = GSS_C_NO_NAME;
534 	conn->context = GSS_C_NO_CONTEXT;
535 
536 	h = g_ascii_strdown (host, -1);
537 	service = g_strconcat ("HTTP@", h, NULL);
538 	token.length = strlen (service);
539 	token.value = (gchar *) service;
540 
541 	maj_stat = gss_import_name (&min_stat,
542 				    &token,
543 				    (gss_OID) GSS_C_NT_HOSTBASED_SERVICE,
544 				    &conn->server_name);
545 
546 	if (GSS_ERROR (maj_stat)) {
547 		soup_gss_error (maj_stat, min_stat, err);
548 		ret = FALSE;
549 		goto out;
550 	}
551 
552 	conn->initialized = TRUE;
553 	ret = TRUE;
554 out:
555 	g_free (h);
556 	g_free (service);
557 	return ret;
558 }
559 
560 static gint
soup_gss_client_step(SoupNegotiateConnectionState * conn,const gchar * challenge,GError ** err)561 soup_gss_client_step (SoupNegotiateConnectionState *conn, const gchar *challenge, GError **err)
562 {
563 	OM_uint32 maj_stat, min_stat;
564 	gss_buffer_desc in = GSS_C_EMPTY_BUFFER;
565 	gss_buffer_desc out = GSS_C_EMPTY_BUFFER;
566 	gint ret = AUTH_GSS_CONTINUE;
567 
568 	g_clear_pointer (&conn->response_header, g_free);
569 
570 	if (challenge && *challenge) {
571 		size_t len;
572 		in.value = g_base64_decode (challenge, &len);
573 		in.length = len;
574 	}
575 
576 	maj_stat = gss_init_sec_context (&min_stat,
577 					 GSS_C_NO_CREDENTIAL,
578 					 &conn->context,
579 					 conn->server_name,
580 					 (gss_OID) &gss_mech_spnego,
581 					 GSS_C_MUTUAL_FLAG,
582 					 GSS_C_INDEFINITE,
583 					 GSS_C_NO_CHANNEL_BINDINGS,
584 					 &in,
585 					 NULL,
586 					 &out,
587 					 NULL,
588 					 NULL);
589 
590 	if ((maj_stat != GSS_S_COMPLETE) && (maj_stat != GSS_S_CONTINUE_NEEDED)) {
591 		soup_gss_error (maj_stat, min_stat, err);
592 		ret = AUTH_GSS_ERROR;
593 		goto out;
594 	}
595 
596 	ret = (maj_stat == GSS_S_COMPLETE) ? AUTH_GSS_COMPLETE : AUTH_GSS_CONTINUE;
597 	if (out.length) {
598 		gchar *response = g_base64_encode ((const guchar *) out.value, out.length);
599 		conn->response_header = g_strconcat ("Negotiate ", response, NULL);
600 		g_free (response);
601 		maj_stat = gss_release_buffer (&min_stat, &out);
602 	}
603 
604 out:
605 	if (out.value)
606 		gss_release_buffer (&min_stat, &out);
607 	if (in.value)
608 		g_free (in.value);
609 	return ret;
610 }
611 
612 static void
soup_gss_client_cleanup(SoupNegotiateConnectionState * conn)613 soup_gss_client_cleanup (SoupNegotiateConnectionState *conn)
614 {
615 	OM_uint32 maj_stat, min_stat;
616 
617 	gss_release_name (&min_stat, &conn->server_name);
618 	maj_stat = gss_delete_sec_context (&min_stat, &conn->context, GSS_C_NO_BUFFER);
619 	if (maj_stat != GSS_S_COMPLETE)
620 		maj_stat = gss_delete_sec_context (&min_stat, &conn->context, GSS_C_NO_BUFFER);
621 }
622 #endif /* LIBSOUP_HAVE_GSSAPI */
623