1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
2 /*
3  * soup-auth-manager.c: SoupAuth manager for SoupSession
4  *
5  * Copyright (C) 2007 Red Hat, Inc.
6  */
7 
8 #ifdef HAVE_CONFIG_H
9 #include <config.h>
10 #endif
11 
12 #include <string.h>
13 
14 #include "soup-auth-manager.h"
15 #include "soup.h"
16 #include "soup-connection-auth.h"
17 #include "soup-message-private.h"
18 #include "soup-message-queue.h"
19 #include "soup-path-map.h"
20 #include "soup-session-private.h"
21 
22 /**
23  * SECTION:soup-auth-manager
24  * @short_description: HTTP client-side authentication handler
25  * @see_also: #SoupSession, #SoupAuth
26  *
27  * #SoupAuthManager is the #SoupSessionFeature that handles HTTP
28  * authentication for a #SoupSession.
29  *
30  * A #SoupAuthManager is added to the session by default, and normally
31  * you don't need to worry about it at all. However, if you want to
32  * disable HTTP authentication, you can remove the feature from the
33  * session with soup_session_remove_feature_by_type(), or disable it on
34  * individual requests with soup_message_disable_feature().
35  *
36  * Since: 2.42
37  **/
38 
39 /**
40  * SOUP_TYPE_AUTH_MANAGER:
41  *
42  * The #GType of #SoupAuthManager; you can use this with
43  * soup_session_remove_feature_by_type() or
44  * soup_message_disable_feature().
45  *
46  * (Although this type has only been publicly visible since libsoup
47  * 2.42, it has always existed in the background, and you can use
48  * <literal><code>g_type_from_name ("SoupAuthManager")</code></literal>
49  * to get its #GType in earlier releases.)
50  *
51  * Since: 2.42
52  */
53 static void soup_auth_manager_session_feature_init (SoupSessionFeatureInterface *feature_interface, gpointer interface_data);
54 static SoupSessionFeatureInterface *soup_session_feature_default_interface;
55 
56 enum {
57 	AUTHENTICATE,
58 	LAST_SIGNAL
59 };
60 
61 static guint signals[LAST_SIGNAL] = { 0 };
62 
63 
64 struct SoupAuthManagerPrivate {
65 	SoupSession *session;
66 	GPtrArray *auth_types;
67 	gboolean auto_ntlm;
68 
69 	GMutex lock;
70 	SoupAuth *proxy_auth;
71 	GHashTable *auth_hosts;
72 };
73 
74 typedef struct {
75 	SoupURI     *uri;
76 	SoupPathMap *auth_realms;      /* path -> scheme:realm */
77 	GHashTable  *auths;            /* scheme:realm -> SoupAuth */
78 } SoupAuthHost;
79 
80 G_DEFINE_TYPE_WITH_CODE (SoupAuthManager, soup_auth_manager, G_TYPE_OBJECT,
81                          G_ADD_PRIVATE (SoupAuthManager)
82 			 G_IMPLEMENT_INTERFACE (SOUP_TYPE_SESSION_FEATURE,
83 						soup_auth_manager_session_feature_init))
84 
85 static void soup_auth_host_free (SoupAuthHost *host);
86 static SoupAuth *record_auth_for_uri (SoupAuthManagerPrivate *priv,
87 				      SoupURI *uri, SoupAuth *auth,
88 				      gboolean prior_auth_failed);
89 
90 static void
soup_auth_manager_init(SoupAuthManager * manager)91 soup_auth_manager_init (SoupAuthManager *manager)
92 {
93 	SoupAuthManagerPrivate *priv;
94 
95 	priv = manager->priv = soup_auth_manager_get_instance_private (manager);
96 
97 	priv->auth_types = g_ptr_array_new_with_free_func ((GDestroyNotify)g_type_class_unref);
98 	priv->auth_hosts = g_hash_table_new_full (soup_uri_host_hash,
99 						  soup_uri_host_equal,
100 						  NULL,
101 						  (GDestroyNotify)soup_auth_host_free);
102 	g_mutex_init (&priv->lock);
103 }
104 
105 static void
soup_auth_manager_finalize(GObject * object)106 soup_auth_manager_finalize (GObject *object)
107 {
108 	SoupAuthManagerPrivate *priv = SOUP_AUTH_MANAGER (object)->priv;
109 
110 	g_ptr_array_free (priv->auth_types, TRUE);
111 
112 	g_hash_table_destroy (priv->auth_hosts);
113 
114 	g_clear_object (&priv->proxy_auth);
115 
116 	g_mutex_clear (&priv->lock);
117 
118 	G_OBJECT_CLASS (soup_auth_manager_parent_class)->finalize (object);
119 }
120 
121 static void
soup_auth_manager_class_init(SoupAuthManagerClass * auth_manager_class)122 soup_auth_manager_class_init (SoupAuthManagerClass *auth_manager_class)
123 {
124 	GObjectClass *object_class = G_OBJECT_CLASS (auth_manager_class);
125 
126 	object_class->finalize = soup_auth_manager_finalize;
127 
128 	/**
129 	 * SoupAuthManager::authenticate:
130 	 * @manager: the #SoupAuthManager
131 	 * @msg: the #SoupMessage being sent
132 	 * @auth: the #SoupAuth to authenticate
133 	 * @retrying: %TRUE if this is the second (or later) attempt
134 	 *
135 	 * Emitted when the manager requires the application to
136 	 * provide authentication credentials.
137 	 *
138 	 * #SoupSession connects to this signal and emits its own
139 	 * #SoupSession::authenticate signal when it is emitted, so
140 	 * you shouldn't need to use this signal directly.
141 	 */
142 	signals[AUTHENTICATE] =
143 		g_signal_new ("authenticate",
144 			      G_OBJECT_CLASS_TYPE (object_class),
145 			      G_SIGNAL_RUN_FIRST,
146 			      G_STRUCT_OFFSET (SoupAuthManagerClass, authenticate),
147 			      NULL, NULL,
148 			      NULL,
149 			      G_TYPE_NONE, 3,
150 			      SOUP_TYPE_MESSAGE,
151 			      SOUP_TYPE_AUTH,
152 			      G_TYPE_BOOLEAN);
153 
154 }
155 
156 static int
auth_type_compare_func(gconstpointer a,gconstpointer b)157 auth_type_compare_func (gconstpointer a, gconstpointer b)
158 {
159 	SoupAuthClass **auth1 = (SoupAuthClass **)a;
160 	SoupAuthClass **auth2 = (SoupAuthClass **)b;
161 
162 	return (*auth1)->strength - (*auth2)->strength;
163 }
164 
165 static gboolean
soup_auth_manager_add_feature(SoupSessionFeature * feature,GType type)166 soup_auth_manager_add_feature (SoupSessionFeature *feature, GType type)
167 {
168 	SoupAuthManagerPrivate *priv = SOUP_AUTH_MANAGER (feature)->priv;
169 	SoupAuthClass *auth_class;
170 
171 	if (!g_type_is_a (type, SOUP_TYPE_AUTH))
172 		return FALSE;
173 
174 	auth_class = g_type_class_ref (type);
175 	g_ptr_array_add (priv->auth_types, auth_class);
176 	g_ptr_array_sort (priv->auth_types, auth_type_compare_func);
177 
178 	/* Plain SoupSession does not get the backward-compat
179 	 * auto-NTLM behavior; SoupSession subclasses do.
180 	 */
181 	if (type == SOUP_TYPE_AUTH_NTLM &&
182 	    G_TYPE_FROM_INSTANCE (priv->session) != SOUP_TYPE_SESSION)
183 		priv->auto_ntlm = TRUE;
184 
185 	return TRUE;
186 }
187 
188 static gboolean
soup_auth_manager_remove_feature(SoupSessionFeature * feature,GType type)189 soup_auth_manager_remove_feature (SoupSessionFeature *feature, GType type)
190 {
191 	SoupAuthManagerPrivate *priv = SOUP_AUTH_MANAGER (feature)->priv;
192 	SoupAuthClass *auth_class;
193 	guint i;
194 
195 	if (!g_type_is_a (type, SOUP_TYPE_AUTH))
196 		return FALSE;
197 
198 	auth_class = g_type_class_peek (type);
199 
200 	for (i = 0; i < priv->auth_types->len; i++) {
201 		if (priv->auth_types->pdata[i] == (gpointer)auth_class) {
202 			if (type == SOUP_TYPE_AUTH_NTLM)
203 				priv->auto_ntlm = FALSE;
204 
205 			g_ptr_array_remove_index (priv->auth_types, i);
206 			return TRUE;
207 		}
208 	}
209 
210 	return FALSE;
211 }
212 
213 static gboolean
soup_auth_manager_has_feature(SoupSessionFeature * feature,GType type)214 soup_auth_manager_has_feature (SoupSessionFeature *feature, GType type)
215 {
216 	SoupAuthManagerPrivate *priv = SOUP_AUTH_MANAGER (feature)->priv;
217 	SoupAuthClass *auth_class;
218 	guint i;
219 
220 	if (!g_type_is_a (type, SOUP_TYPE_AUTH))
221 		return FALSE;
222 
223 	auth_class = g_type_class_peek (type);
224 	for (i = 0; i < priv->auth_types->len; i++) {
225 		if (priv->auth_types->pdata[i] == (gpointer)auth_class)
226 			return TRUE;
227 	}
228 	return FALSE;
229 }
230 
231 static void
soup_auth_manager_attach(SoupSessionFeature * feature,SoupSession * session)232 soup_auth_manager_attach (SoupSessionFeature *feature, SoupSession *session)
233 {
234 	SoupAuthManagerPrivate *priv = SOUP_AUTH_MANAGER (feature)->priv;
235 
236 	/* FIXME: should support multiple sessions */
237 	priv->session = session;
238 
239 	soup_session_feature_default_interface->attach (feature, session);
240 }
241 
242 static inline const char *
auth_header_for_message(SoupMessage * msg)243 auth_header_for_message (SoupMessage *msg)
244 {
245 	if (msg->status_code == SOUP_STATUS_PROXY_UNAUTHORIZED) {
246 		return soup_message_headers_get_list (msg->response_headers,
247 						      "Proxy-Authenticate");
248 	} else {
249 		return soup_message_headers_get_list (msg->response_headers,
250 						      "WWW-Authenticate");
251 	}
252 }
253 
254 static GSList *
next_challenge_start(GSList * items)255 next_challenge_start (GSList *items)
256 {
257 	/* The relevant grammar (from httpbis):
258 	 *
259 	 * WWW-Authenticate   = 1#challenge
260 	 * Proxy-Authenticate = 1#challenge
261 	 * challenge          = auth-scheme [ 1*SP ( b64token / #auth-param ) ]
262 	 * auth-scheme        = token
263 	 * auth-param         = token BWS "=" BWS ( token / quoted-string )
264 	 * b64token           = 1*( ALPHA / DIGIT /
265 	 *                          "-" / "." / "_" / "~" / "+" / "/" ) *"="
266 	 *
267 	 * The fact that quoted-strings can contain commas, equals
268 	 * signs, and auth scheme names makes it tricky to "cheat" on
269 	 * the parsing. So soup_auth_manager_extract_challenge() will
270 	 * have used soup_header_parse_list() to split the header into
271 	 * items. Given the grammar above, the possible items are:
272 	 *
273 	 *   auth-scheme
274 	 *   auth-scheme 1*SP b64token
275 	 *   auth-scheme 1*SP auth-param
276 	 *   auth-param
277 	 *
278 	 * where the first three represent the start of a new challenge and
279 	 * the last one does not.
280 	 */
281 
282 	for (; items; items = items->next) {
283 		const char *item = items->data;
284 		const char *sp = strpbrk (item, "\t\r\n ");
285 		const char *eq = strchr (item, '=');
286 
287 		if (!eq) {
288 			/* No "=", so it can't be an auth-param */
289 			return items;
290 		}
291 		if (!sp || sp > eq) {
292 			/* No space, or first space appears after the "=",
293 			 * so it must be an auth-param.
294 			 */
295 			continue;
296 		}
297 		while (g_ascii_isspace (*++sp))
298 			;
299 		if (sp == eq) {
300 			/* First "=" appears immediately after the first
301 			 * space, so this must be an auth-param with
302 			 * space around the "=".
303 			 */
304 			continue;
305 		}
306 
307 		/* "auth-scheme auth-param" or "auth-scheme b64token" */
308 		return items;
309 	}
310 
311 	return NULL;
312 }
313 
314 static char *
soup_auth_manager_extract_challenge(const char * challenges,const char * scheme)315 soup_auth_manager_extract_challenge (const char *challenges, const char *scheme)
316 {
317 	GSList *items, *i, *next;
318 	int schemelen = strlen (scheme);
319 	char *item;
320 	GString *challenge;
321 
322 	items = soup_header_parse_list (challenges);
323 
324 	/* First item will start with the scheme name, followed by
325 	 * either nothing, or else a space and then the first
326 	 * auth-param.
327 	 */
328 	for (i = items; i; i = next_challenge_start (i->next)) {
329 		item = i->data;
330 		if (!g_ascii_strncasecmp (item, scheme, schemelen) &&
331 		    (!item[schemelen] || g_ascii_isspace (item[schemelen])))
332 			break;
333 	}
334 	if (!i) {
335 		soup_header_free_list (items);
336 		return NULL;
337 	}
338 
339 	next = next_challenge_start (i->next);
340 	challenge = g_string_new (item);
341 	for (i = i->next; i != next; i = i->next) {
342 		item = i->data;
343 		g_string_append (challenge, ", ");
344 		g_string_append (challenge, item);
345 	}
346 
347 	soup_header_free_list (items);
348 	return g_string_free (challenge, FALSE);
349 }
350 
351 static SoupAuth *
create_auth(SoupAuthManagerPrivate * priv,SoupMessage * msg)352 create_auth (SoupAuthManagerPrivate *priv, SoupMessage *msg)
353 {
354 	const char *header;
355 	SoupAuthClass *auth_class;
356 	char *challenge = NULL;
357 	SoupAuth *auth = NULL;
358 	int i;
359 
360 	header = auth_header_for_message (msg);
361 	if (!header)
362 		return NULL;
363 
364 	for (i = priv->auth_types->len - 1; i >= 0; i--) {
365 		auth_class = priv->auth_types->pdata[i];
366 		challenge = soup_auth_manager_extract_challenge (header, auth_class->scheme_name);
367 		if (!challenge)
368 			continue;
369 		auth = soup_auth_new (G_TYPE_FROM_CLASS (auth_class), msg, challenge);
370 		g_free (challenge);
371 		if (auth)
372 			break;
373 	}
374 
375 	return auth;
376 }
377 
378 static gboolean
check_auth(SoupMessage * msg,SoupAuth * auth)379 check_auth (SoupMessage *msg, SoupAuth *auth)
380 {
381 	const char *header, *scheme;
382 	char *challenge = NULL;
383 	gboolean ok = TRUE;
384 
385 	scheme = soup_auth_get_scheme_name (auth);
386 
387 	header = auth_header_for_message (msg);
388 	if (header)
389 		challenge = soup_auth_manager_extract_challenge (header, scheme);
390 	if (!challenge) {
391 		ok = FALSE;
392 		challenge = g_strdup (scheme);
393 	}
394 
395 	if (!soup_auth_update (auth, msg, challenge))
396 		ok = FALSE;
397 	g_free (challenge);
398 	return ok;
399 }
400 
401 static SoupAuthHost *
get_auth_host_for_uri(SoupAuthManagerPrivate * priv,SoupURI * uri)402 get_auth_host_for_uri (SoupAuthManagerPrivate *priv, SoupURI *uri)
403 {
404 	SoupAuthHost *host;
405 
406 	host = g_hash_table_lookup (priv->auth_hosts, uri);
407 	if (host)
408 		return host;
409 
410 	host = g_slice_new0 (SoupAuthHost);
411 	host->uri = soup_uri_copy_host (uri);
412 	g_hash_table_insert (priv->auth_hosts, host->uri, host);
413 
414 	return host;
415 }
416 
417 static void
soup_auth_host_free(SoupAuthHost * host)418 soup_auth_host_free (SoupAuthHost *host)
419 {
420 	g_clear_pointer (&host->auth_realms, soup_path_map_free);
421 	g_clear_pointer (&host->auths, g_hash_table_destroy);
422 
423 	soup_uri_free (host->uri);
424 	g_slice_free (SoupAuthHost, host);
425 }
426 
427 static gboolean
make_auto_ntlm_auth(SoupAuthManagerPrivate * priv,SoupAuthHost * host)428 make_auto_ntlm_auth (SoupAuthManagerPrivate *priv, SoupAuthHost *host)
429 {
430 	SoupAuth *auth;
431 
432 	if (!priv->auto_ntlm)
433 		return FALSE;
434 
435 	auth = g_object_new (SOUP_TYPE_AUTH_NTLM,
436 			     SOUP_AUTH_HOST, host->uri->host,
437 			     NULL);
438 	record_auth_for_uri (priv, host->uri, auth, FALSE);
439 	g_object_unref (auth);
440 	return TRUE;
441 }
442 
443 static void
update_authorization_header(SoupMessage * msg,SoupAuth * auth,gboolean is_proxy)444 update_authorization_header (SoupMessage *msg, SoupAuth *auth, gboolean is_proxy)
445 {
446 	const char *authorization_header = is_proxy ? "Proxy-Authorization" : "Authorization";
447 	char *token;
448 
449 	if (soup_message_get_auth (msg))
450 		soup_message_headers_remove (msg->request_headers, authorization_header);
451 
452 	if (!auth)
453 		return;
454 
455 	token = soup_auth_get_authorization (auth, msg);
456 	if (!token)
457 		return;
458 
459 	soup_message_headers_replace (msg->request_headers, authorization_header, token);
460 	g_free (token);
461 }
462 
463 static SoupAuth *
lookup_auth(SoupAuthManagerPrivate * priv,SoupMessage * msg)464 lookup_auth (SoupAuthManagerPrivate *priv, SoupMessage *msg)
465 {
466 	SoupAuthHost *host;
467 	const char *path, *realm;
468 	SoupAuth *auth;
469 
470 	/* If the message already has a ready auth, use that instead */
471 	auth = soup_message_get_auth (msg);
472 	if (auth && soup_auth_is_ready (auth, msg))
473 		return auth;
474 
475 	if (soup_message_get_flags (msg) & SOUP_MESSAGE_DO_NOT_USE_AUTH_CACHE)
476 		return NULL;
477 
478 	host = get_auth_host_for_uri (priv, soup_message_get_uri (msg));
479 	if (!host->auth_realms && !make_auto_ntlm_auth (priv, host))
480 		return NULL;
481 
482 	/* Cannot change the above '&&' into '||', because make_auto_ntlm_auth() is used
483 	 * to populate host->auth_realms when it's not set yet. Even the make_auto_ntlm_auth()
484 	 * returns TRUE only if it also populates the host->auth_realms, this extra test
485 	 * is required to mute a FORWARD_NULL Coverity Scan warning, which is a false-positive
486 	 * here */
487 	if (!host->auth_realms)
488 		return NULL;
489 
490 	path = soup_message_get_uri (msg)->path;
491 	if (!path)
492 		path = "/";
493 	realm = soup_path_map_lookup (host->auth_realms, path);
494 	if (realm)
495 		return g_hash_table_lookup (host->auths, realm);
496 
497 	return NULL;
498 }
499 
500 static SoupAuth *
lookup_proxy_auth(SoupAuthManagerPrivate * priv,SoupMessage * msg)501 lookup_proxy_auth (SoupAuthManagerPrivate *priv, SoupMessage *msg)
502 {
503 	SoupAuth *auth;
504 
505 	/* If the message already has a ready auth, use that instead */
506 	auth = soup_message_get_proxy_auth (msg);
507 	if (auth && soup_auth_is_ready (auth, msg))
508 		return auth;
509 
510 	if (soup_message_get_flags (msg) & SOUP_MESSAGE_DO_NOT_USE_AUTH_CACHE)
511 		return NULL;
512 
513 	return priv->proxy_auth;
514 }
515 
516 static void
authenticate_auth(SoupAuthManager * manager,SoupAuth * auth,SoupMessage * msg,gboolean prior_auth_failed,gboolean proxy,gboolean can_interact)517 authenticate_auth (SoupAuthManager *manager, SoupAuth *auth,
518 		   SoupMessage *msg, gboolean prior_auth_failed,
519 		   gboolean proxy, gboolean can_interact)
520 {
521 	SoupAuthManagerPrivate *priv = manager->priv;
522 	SoupURI *uri;
523 
524 	if (!soup_auth_can_authenticate (auth))
525 		return;
526 
527 	if (proxy) {
528 		SoupMessageQueue *queue;
529 		SoupMessageQueueItem *item;
530 
531 		queue = soup_session_get_queue (priv->session);
532 		item = soup_message_queue_lookup (queue, msg);
533 		if (!item)
534 			return;
535 
536 		/* When loaded from the disk cache, the connection is NULL. */
537 		uri = item->conn ? soup_connection_get_proxy_uri (item->conn) : NULL;
538 		soup_message_queue_item_unref (item);
539 		if (!uri)
540 			return;
541 	} else
542 		uri = soup_message_get_uri (msg);
543 
544 	/* If a password is specified explicitly in the URI, use it
545 	 * even if the auth had previously already been authenticated.
546 	 */
547 	if (uri->password && uri->user) {
548 		soup_auth_authenticate (auth, uri->user, uri->password);
549 		soup_uri_set_password (uri, NULL);
550 		soup_uri_set_user (uri, NULL);
551 	} else if (!soup_auth_is_authenticated (auth) && can_interact) {
552 		g_signal_emit (manager, signals[AUTHENTICATE], 0,
553 			       msg, auth, prior_auth_failed);
554 	}
555 }
556 
557 static SoupAuth *
record_auth_for_uri(SoupAuthManagerPrivate * priv,SoupURI * uri,SoupAuth * auth,gboolean prior_auth_failed)558 record_auth_for_uri (SoupAuthManagerPrivate *priv, SoupURI *uri,
559 		     SoupAuth *auth, gboolean prior_auth_failed)
560 {
561 	SoupAuthHost *host;
562 	SoupAuth *old_auth;
563 	const char *path;
564 	char *auth_info, *old_auth_info;
565 	GSList *pspace, *p;
566 
567 	host = get_auth_host_for_uri (priv, uri);
568 	auth_info = soup_auth_get_info (auth);
569 
570 	if (!host->auth_realms) {
571 		host->auth_realms = soup_path_map_new (g_free);
572 		host->auths = g_hash_table_new_full (g_str_hash, g_str_equal,
573 						     g_free, g_object_unref);
574 	}
575 
576 	/* Record where this auth realm is used. */
577 	pspace = soup_auth_get_protection_space (auth, uri);
578 	for (p = pspace; p; p = p->next) {
579 		path = p->data;
580 		old_auth_info = soup_path_map_lookup (host->auth_realms, path);
581 		if (old_auth_info) {
582 			if (!strcmp (old_auth_info, auth_info))
583 				continue;
584 			soup_path_map_remove (host->auth_realms, path);
585 		}
586 
587 		soup_path_map_add (host->auth_realms, path,
588 				   g_strdup (auth_info));
589 	}
590 	soup_auth_free_protection_space (auth, pspace);
591 
592 	/* Now, make sure the auth is recorded. (If there's a
593 	 * pre-existing good auth, we keep that rather than the new one,
594 	 * since the old one might already be authenticated.)
595 	 */
596 	old_auth = g_hash_table_lookup (host->auths, auth_info);
597 	if (old_auth && (old_auth != auth || !prior_auth_failed)) {
598 		g_free (auth_info);
599 		return old_auth;
600 	} else {
601 		g_hash_table_insert (host->auths, auth_info,
602 				     g_object_ref (auth));
603 		return auth;
604 	}
605 }
606 
607 static void
auth_got_headers(SoupMessage * msg,gpointer manager)608 auth_got_headers (SoupMessage *msg, gpointer manager)
609 {
610 	SoupAuthManagerPrivate *priv = SOUP_AUTH_MANAGER (manager)->priv;
611 	SoupAuth *auth, *prior_auth;
612 	gboolean prior_auth_failed = FALSE;
613 
614 	g_mutex_lock (&priv->lock);
615 
616 	/* See if we used auth last time */
617 	prior_auth = soup_message_get_auth (msg);
618 	if (prior_auth && check_auth (msg, prior_auth)) {
619 		auth = g_object_ref (prior_auth);
620 		if (!soup_auth_is_ready (auth, msg))
621 			prior_auth_failed = TRUE;
622 	} else {
623 		auth = create_auth (priv, msg);
624 		if (!auth) {
625 			g_mutex_unlock (&priv->lock);
626 			return;
627 		}
628 	}
629 
630 	if (!(soup_message_get_flags (msg) & SOUP_MESSAGE_DO_NOT_USE_AUTH_CACHE)) {
631 		SoupAuth *new_auth;
632 
633 		new_auth = record_auth_for_uri (priv, soup_message_get_uri (msg),
634 						auth, prior_auth_failed);
635 		g_object_unref (auth);
636 		auth = g_object_ref (new_auth);
637 	}
638 
639 	/* If we need to authenticate, try to do it. */
640 	authenticate_auth (manager, auth, msg,
641 			   prior_auth_failed, FALSE, TRUE);
642 	soup_message_set_auth (msg, auth);
643 	g_object_unref (auth);
644 	g_mutex_unlock (&priv->lock);
645 }
646 
647 static void
auth_got_body(SoupMessage * msg,gpointer manager)648 auth_got_body (SoupMessage *msg, gpointer manager)
649 {
650 	SoupAuthManagerPrivate *priv = SOUP_AUTH_MANAGER (manager)->priv;
651 	SoupAuth *auth;
652 
653 	g_mutex_lock (&priv->lock);
654 	auth = lookup_auth (priv, msg);
655 	if (auth && soup_auth_is_ready (auth, msg)) {
656 		if (SOUP_IS_CONNECTION_AUTH (auth)) {
657 			SoupMessageFlags flags;
658 
659 			flags = soup_message_get_flags (msg);
660 			soup_message_set_flags (msg, flags & ~SOUP_MESSAGE_NEW_CONNECTION);
661 		}
662 
663 		/* When not using cached credentials, update the Authorization header
664 		 * right before requeuing the message.
665 		 */
666 		if (soup_message_get_flags (msg) & SOUP_MESSAGE_DO_NOT_USE_AUTH_CACHE)
667 			update_authorization_header (msg, auth, FALSE);
668 
669 		soup_session_requeue_message (priv->session, msg);
670 	}
671 	g_mutex_unlock (&priv->lock);
672 }
673 
674 static void
proxy_auth_got_headers(SoupMessage * msg,gpointer manager)675 proxy_auth_got_headers (SoupMessage *msg, gpointer manager)
676 {
677 	SoupAuthManagerPrivate *priv = SOUP_AUTH_MANAGER (manager)->priv;
678 	SoupAuth *auth = NULL, *prior_auth;
679 	gboolean prior_auth_failed = FALSE;
680 
681 	g_mutex_lock (&priv->lock);
682 
683 	/* See if we used auth last time */
684 	prior_auth = soup_message_get_proxy_auth (msg);
685 	if (prior_auth && check_auth (msg, prior_auth)) {
686 		if (!soup_auth_is_ready (prior_auth, msg))
687 			prior_auth_failed = TRUE;
688 	}
689 
690 	if (!(soup_message_get_flags (msg) & SOUP_MESSAGE_DO_NOT_USE_AUTH_CACHE))
691 		auth = priv->proxy_auth ? g_object_ref (priv->proxy_auth) : NULL;
692 
693 	if (!auth) {
694 		auth = create_auth (priv, msg);
695 		if (!auth) {
696 			g_mutex_unlock (&priv->lock);
697 			return;
698 		}
699 		if (!(soup_message_get_flags (msg) & SOUP_MESSAGE_DO_NOT_USE_AUTH_CACHE))
700 			priv->proxy_auth = g_object_ref (auth);
701 	}
702 
703 	/* If we need to authenticate, try to do it. */
704 	authenticate_auth (manager, auth, msg,
705 			   prior_auth_failed, TRUE, TRUE);
706 	soup_message_set_proxy_auth (msg, auth);
707 	g_object_unref (auth);
708 	g_mutex_unlock (&priv->lock);
709 }
710 
711 static void
proxy_auth_got_body(SoupMessage * msg,gpointer manager)712 proxy_auth_got_body (SoupMessage *msg, gpointer manager)
713 {
714 	SoupAuthManagerPrivate *priv = SOUP_AUTH_MANAGER (manager)->priv;
715 	SoupAuth *auth;
716 
717 	g_mutex_lock (&priv->lock);
718 
719 	auth = lookup_proxy_auth (priv, msg);
720 	if (auth && soup_auth_is_ready (auth, msg)) {
721 		/* When not using cached credentials, update the Authorization header
722 		 * right before requeuing the message.
723 		 */
724 		if (soup_message_get_flags (msg) & SOUP_MESSAGE_DO_NOT_USE_AUTH_CACHE)
725 			update_authorization_header (msg, auth, TRUE);
726 		soup_session_requeue_message (priv->session, msg);
727 	}
728 
729 	g_mutex_unlock (&priv->lock);
730 }
731 
732 static void
auth_msg_starting(SoupMessage * msg,gpointer manager)733 auth_msg_starting (SoupMessage *msg, gpointer manager)
734 {
735 	SoupAuthManagerPrivate *priv = SOUP_AUTH_MANAGER (manager)->priv;
736 	SoupAuth *auth;
737 
738 	if (soup_message_get_flags (msg) & SOUP_MESSAGE_DO_NOT_USE_AUTH_CACHE)
739 		return;
740 
741 	g_mutex_lock (&priv->lock);
742 
743 	if (msg->method != SOUP_METHOD_CONNECT) {
744 		auth = lookup_auth (priv, msg);
745 		if (auth) {
746 			authenticate_auth (manager, auth, msg, FALSE, FALSE, FALSE);
747 			if (!soup_auth_is_ready (auth, msg))
748 				auth = NULL;
749 		}
750 		soup_message_set_auth (msg, auth);
751 		update_authorization_header (msg, auth, FALSE);
752 	}
753 
754 	auth = lookup_proxy_auth (priv, msg);
755 	if (auth) {
756 		authenticate_auth (manager, auth, msg, FALSE, TRUE, FALSE);
757 		if (!soup_auth_is_ready (auth, msg))
758 			auth = NULL;
759 	}
760 	soup_message_set_proxy_auth (msg, auth);
761 	update_authorization_header (msg, auth, TRUE);
762 
763 	g_mutex_unlock (&priv->lock);
764 }
765 
766 static void
soup_auth_manager_request_queued(SoupSessionFeature * manager,SoupSession * session,SoupMessage * msg)767 soup_auth_manager_request_queued (SoupSessionFeature *manager,
768 				  SoupSession *session,
769 				  SoupMessage *msg)
770 {
771 	g_signal_connect (msg, "starting",
772 			  G_CALLBACK (auth_msg_starting), manager);
773 
774 	soup_message_add_status_code_handler (
775 		msg, "got_headers", SOUP_STATUS_UNAUTHORIZED,
776 		G_CALLBACK (auth_got_headers), manager);
777 	soup_message_add_status_code_handler (
778 		msg, "got_body", SOUP_STATUS_UNAUTHORIZED,
779 		G_CALLBACK (auth_got_body), manager);
780 
781 	soup_message_add_status_code_handler (
782 		msg, "got_headers", SOUP_STATUS_PROXY_UNAUTHORIZED,
783 		G_CALLBACK (proxy_auth_got_headers), manager);
784 	soup_message_add_status_code_handler (
785 		msg, "got_body", SOUP_STATUS_PROXY_UNAUTHORIZED,
786 		G_CALLBACK (proxy_auth_got_body), manager);
787 }
788 
789 static void
soup_auth_manager_request_unqueued(SoupSessionFeature * manager,SoupSession * session,SoupMessage * msg)790 soup_auth_manager_request_unqueued (SoupSessionFeature *manager,
791 				    SoupSession *session,
792 				    SoupMessage *msg)
793 {
794 	g_signal_handlers_disconnect_matched (msg, G_SIGNAL_MATCH_DATA,
795 					      0, 0, NULL, NULL, manager);
796 }
797 
798 /**
799  * soup_auth_manager_use_auth:
800  * @manager: a #SoupAuthManager
801  * @uri: the #SoupURI under which @auth is to be used
802  * @auth: the #SoupAuth to use
803  *
804  * Records that @auth is to be used under @uri, as though a
805  * WWW-Authenticate header had been received at that URI. This can be
806  * used to "preload" @manager's auth cache, to avoid an extra HTTP
807  * round trip in the case where you know ahead of time that a 401
808  * response will be returned.
809  *
810  * This is only useful for authentication types where the initial
811  * Authorization header does not depend on any additional information
812  * from the server. (Eg, Basic or NTLM, but not Digest.)
813  *
814  * Since: 2.42
815  */
816 void
soup_auth_manager_use_auth(SoupAuthManager * manager,SoupURI * uri,SoupAuth * auth)817 soup_auth_manager_use_auth (SoupAuthManager *manager,
818 			    SoupURI         *uri,
819 			    SoupAuth        *auth)
820 {
821 	SoupAuthManagerPrivate *priv = manager->priv;
822 
823 	g_mutex_lock (&priv->lock);
824 	record_auth_for_uri (priv, uri, auth, FALSE);
825 	g_mutex_unlock (&priv->lock);
826 }
827 
828 /**
829  * soup_auth_manager_clear_cached_credentials:
830  * @manager: a #SoupAuthManager
831  *
832  * Clear all credentials cached by @manager
833  *
834  * Since: 2.58
835  */
836 void
soup_auth_manager_clear_cached_credentials(SoupAuthManager * manager)837 soup_auth_manager_clear_cached_credentials (SoupAuthManager *manager)
838 {
839 	SoupAuthManagerPrivate *priv;
840 
841 	g_return_if_fail (SOUP_IS_AUTH_MANAGER (manager));
842 
843 	priv = manager->priv;
844 	g_mutex_lock (&priv->lock);
845 	g_hash_table_remove_all (priv->auth_hosts);
846 	g_mutex_unlock (&priv->lock);
847 }
848 
849 static void
soup_auth_manager_session_feature_init(SoupSessionFeatureInterface * feature_interface,gpointer interface_data)850 soup_auth_manager_session_feature_init (SoupSessionFeatureInterface *feature_interface,
851 					gpointer interface_data)
852 {
853 	soup_session_feature_default_interface =
854 		g_type_default_interface_peek (SOUP_TYPE_SESSION_FEATURE);
855 
856 	feature_interface->attach = soup_auth_manager_attach;
857 	feature_interface->request_queued = soup_auth_manager_request_queued;
858 	feature_interface->request_unqueued = soup_auth_manager_request_unqueued;
859 	feature_interface->add_feature = soup_auth_manager_add_feature;
860 	feature_interface->remove_feature = soup_auth_manager_remove_feature;
861 	feature_interface->has_feature = soup_auth_manager_has_feature;
862 }
863