Implement SoupProxyURIResolver, to replace SoupProxyResolver
[platform/upstream/libsoup.git] / libsoup / soup-auth-manager.c
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-address.h"
16 #include "soup-headers.h"
17 #include "soup-marshal.h"
18 #include "soup-message-private.h"
19 #include "soup-message-queue.h"
20 #include "soup-path-map.h"
21 #include "soup-session.h"
22 #include "soup-session-feature.h"
23 #include "soup-session-private.h"
24 #include "soup-uri.h"
25
26 static void soup_auth_manager_session_feature_init (SoupSessionFeatureInterface *feature_interface, gpointer interface_data);
27 static SoupSessionFeatureInterface *soup_session_feature_default_interface;
28
29 static void attach (SoupSessionFeature *feature, SoupSession *session);
30 static void request_queued  (SoupSessionFeature *feature, SoupSession *session,
31                              SoupMessage *msg);
32 static void request_started  (SoupSessionFeature *feature, SoupSession *session,
33                               SoupMessage *msg, SoupSocket *socket);
34 static void request_unqueued  (SoupSessionFeature *feature,
35                                SoupSession *session, SoupMessage *msg);
36
37 enum {
38         AUTHENTICATE,
39         LAST_SIGNAL
40 };
41
42 static guint signals[LAST_SIGNAL] = { 0 };
43
44 G_DEFINE_TYPE_WITH_CODE (SoupAuthManager, soup_auth_manager, G_TYPE_OBJECT,
45                          G_IMPLEMENT_INTERFACE (SOUP_TYPE_SESSION_FEATURE,
46                                                 soup_auth_manager_session_feature_init))
47
48 typedef struct {
49         SoupSession *session;
50         GPtrArray *auth_types;
51
52         SoupAuth *proxy_auth;
53         GHashTable *auth_hosts;
54 } SoupAuthManagerPrivate;
55 #define SOUP_AUTH_MANAGER_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), SOUP_TYPE_AUTH_MANAGER, SoupAuthManagerPrivate))
56
57 typedef struct {
58         SoupURI     *uri;
59         SoupPathMap *auth_realms;      /* path -> scheme:realm */
60         GHashTable  *auths;            /* scheme:realm -> SoupAuth */
61 } SoupAuthHost;
62
63 static void
64 soup_auth_manager_init (SoupAuthManager *manager)
65 {
66         SoupAuthManagerPrivate *priv = SOUP_AUTH_MANAGER_GET_PRIVATE (manager);
67
68         priv->auth_types = g_ptr_array_new ();
69         priv->auth_hosts = g_hash_table_new (soup_uri_host_hash,
70                                              soup_uri_host_equal);
71 }
72
73 static gboolean
74 foreach_free_host (gpointer key, gpointer value, gpointer data)
75 {
76         SoupAuthHost *host = value;
77
78         if (host->auth_realms)
79                 soup_path_map_free (host->auth_realms);
80         if (host->auths)
81                 g_hash_table_destroy (host->auths);
82
83         soup_uri_free (host->uri);
84         g_slice_free (SoupAuthHost, host);
85
86         return TRUE;
87 }
88
89 static void
90 finalize (GObject *object)
91 {
92         SoupAuthManagerPrivate *priv = SOUP_AUTH_MANAGER_GET_PRIVATE (object);
93         int i;
94
95         for (i = 0; i < priv->auth_types->len; i++)
96                 g_type_class_unref (priv->auth_types->pdata[i]);
97         g_ptr_array_free (priv->auth_types, TRUE);
98
99         g_hash_table_foreach_remove (priv->auth_hosts, foreach_free_host, NULL);
100         g_hash_table_destroy (priv->auth_hosts);
101
102         if (priv->proxy_auth)
103                 g_object_unref (priv->proxy_auth);
104
105         G_OBJECT_CLASS (soup_auth_manager_parent_class)->finalize (object);
106 }
107
108 static void
109 soup_auth_manager_class_init (SoupAuthManagerClass *auth_manager_class)
110 {
111         GObjectClass *object_class = G_OBJECT_CLASS (auth_manager_class);
112
113         g_type_class_add_private (auth_manager_class, sizeof (SoupAuthManagerPrivate));
114
115         object_class->finalize = finalize;
116
117         signals[AUTHENTICATE] =
118                 g_signal_new ("authenticate",
119                               G_OBJECT_CLASS_TYPE (object_class),
120                               G_SIGNAL_RUN_FIRST,
121                               G_STRUCT_OFFSET (SoupAuthManagerClass, authenticate),
122                               NULL, NULL,
123                               soup_marshal_NONE__OBJECT_OBJECT_BOOLEAN,
124                               G_TYPE_NONE, 3,
125                               SOUP_TYPE_MESSAGE,
126                               SOUP_TYPE_AUTH,
127                               G_TYPE_BOOLEAN);
128
129 }
130
131 static void
132 soup_auth_manager_session_feature_init (SoupSessionFeatureInterface *feature_interface,
133                                         gpointer interface_data)
134 {
135         soup_session_feature_default_interface =
136                 g_type_default_interface_peek (SOUP_TYPE_SESSION_FEATURE);
137
138         feature_interface->attach = attach;
139         feature_interface->request_queued = request_queued;
140         feature_interface->request_started = request_started;
141         feature_interface->request_unqueued = request_unqueued;
142 }
143
144 static int
145 auth_type_compare_func (gconstpointer a, gconstpointer b)
146 {
147         SoupAuthClass **auth1 = (SoupAuthClass **)a;
148         SoupAuthClass **auth2 = (SoupAuthClass **)b;
149
150         return (*auth1)->strength - (*auth2)->strength;
151 }
152
153 void
154 soup_auth_manager_add_type (SoupAuthManager *manager, GType type)
155 {
156         SoupAuthManagerPrivate *priv = SOUP_AUTH_MANAGER_GET_PRIVATE (manager);
157         SoupAuthClass *auth_class;
158
159         g_return_if_fail (g_type_is_a (type, SOUP_TYPE_AUTH));
160
161         auth_class = g_type_class_ref (type);
162         g_ptr_array_add (priv->auth_types, auth_class);
163         g_ptr_array_sort (priv->auth_types, auth_type_compare_func);
164 }
165
166 void
167 soup_auth_manager_remove_type (SoupAuthManager *manager, GType type)
168 {
169         SoupAuthManagerPrivate *priv = SOUP_AUTH_MANAGER_GET_PRIVATE (manager);
170         SoupAuthClass *auth_class;
171         int i;
172
173         g_return_if_fail (g_type_is_a (type, SOUP_TYPE_AUTH));
174
175         auth_class = g_type_class_peek (type);
176         for (i = 0; i < priv->auth_types->len; i++) {
177                 if (priv->auth_types->pdata[i] == (gpointer)auth_class) {
178                         g_ptr_array_remove_index (priv->auth_types, i);
179                         g_type_class_unref (auth_class);
180                         return;
181                 }
182         }
183 }
184
185 void
186 soup_auth_manager_emit_authenticate (SoupAuthManager *manager, SoupMessage *msg,
187                                      SoupAuth *auth, gboolean retrying)
188 {
189         g_signal_emit (manager, signals[AUTHENTICATE], 0, msg, auth, retrying);
190 }
191
192 static void
193 attach (SoupSessionFeature *manager, SoupSession *session)
194 {
195         SoupAuthManagerPrivate *priv = SOUP_AUTH_MANAGER_GET_PRIVATE (manager);
196
197         /* FIXME: should support multiple sessions */
198         priv->session = session;
199
200         soup_session_feature_default_interface->attach (manager, session);
201 }
202
203 static inline const char *
204 auth_header_for_message (SoupMessage *msg)
205 {
206         if (msg->status_code == SOUP_STATUS_PROXY_UNAUTHORIZED) {
207                 return soup_message_headers_get_list (msg->response_headers,
208                                                       "Proxy-Authenticate");
209         } else {
210                 return soup_message_headers_get_list (msg->response_headers,
211                                                       "WWW-Authenticate");
212         }
213 }
214
215 static char *
216 extract_challenge (const char *challenges, const char *scheme)
217 {
218         GSList *items, *i;
219         int schemelen = strlen (scheme);
220         char *item, *space, *equals;
221         GString *challenge;
222
223         /* The relevant grammar:
224          *
225          * WWW-Authenticate   = 1#challenge
226          * Proxy-Authenticate = 1#challenge
227          * challenge          = auth-scheme 1#auth-param
228          * auth-scheme        = token
229          * auth-param         = token "=" ( token | quoted-string )
230          *
231          * The fact that quoted-strings can contain commas, equals
232          * signs, and auth scheme names makes it tricky to "cheat" on
233          * the parsing. We just use soup_header_parse_list(), and then
234          * reassemble the pieces after we find the one we want.
235          */
236
237         items = soup_header_parse_list (challenges);
238
239         /* First item will start with the scheme name, followed by a
240          * space and then the first auth-param.
241          */
242         for (i = items; i; i = i->next) {
243                 item = i->data;
244                 if (!g_ascii_strncasecmp (item, scheme, schemelen) &&
245                     g_ascii_isspace (item[schemelen]))
246                         break;
247         }
248         if (!i) {
249                 soup_header_free_list (items);
250                 return NULL;
251         }
252
253         /* The challenge extends from this item until the end, or until
254          * the next item that has a space before an equals sign.
255          */
256         challenge = g_string_new (item);
257         for (i = i->next; i; i = i->next) {
258                 item = i->data;
259                 space = strpbrk (item, " \t");
260                 equals = strchr (item, '=');
261                 if (!equals || (space && equals > space))
262                         break;
263
264                 g_string_append (challenge, ", ");
265                 g_string_append (challenge, item);
266         }
267
268         soup_header_free_list (items);
269         return g_string_free (challenge, FALSE);
270 }
271
272 static SoupAuth *
273 create_auth (SoupAuthManagerPrivate *priv, SoupMessage *msg)
274 {
275         const char *header;
276         SoupAuthClass *auth_class;
277         char *challenge = NULL;
278         SoupAuth *auth;
279         int i;
280
281         header = auth_header_for_message (msg);
282         if (!header)
283                 return NULL;
284
285         for (i = priv->auth_types->len - 1; i >= 0; i--) {
286                 auth_class = priv->auth_types->pdata[i];
287                 challenge = extract_challenge (header, auth_class->scheme_name);
288                 if (challenge)
289                         break;
290         }
291         if (!challenge)
292                 return NULL;
293
294         auth = soup_auth_new (G_TYPE_FROM_CLASS (auth_class), msg, challenge);
295         g_free (challenge);
296         return auth;
297 }
298
299 static gboolean
300 check_auth (SoupMessage *msg, SoupAuth *auth)
301 {
302         const char *header;
303         char *challenge;
304         gboolean ok;
305
306         header = auth_header_for_message (msg);
307         if (!header)
308                 return FALSE;
309
310         challenge = extract_challenge (header, soup_auth_get_scheme_name (auth));
311         if (!challenge)
312                 return FALSE;
313
314         ok = soup_auth_update (auth, msg, challenge);
315         g_free (challenge);
316         return ok;
317 }
318
319 static SoupAuthHost *
320 get_auth_host_for_message (SoupAuthManagerPrivate *priv, SoupMessage *msg)
321 {
322         SoupAuthHost *host;
323         SoupURI *uri = soup_message_get_uri (msg);
324
325         host = g_hash_table_lookup (priv->auth_hosts, uri);
326         if (host)
327                 return host;
328
329         host = g_slice_new0 (SoupAuthHost);
330         host->uri = soup_uri_copy_host (uri);
331         g_hash_table_insert (priv->auth_hosts, host->uri, host);
332
333         return host;
334 }
335
336 static SoupAuth *
337 lookup_auth (SoupAuthManagerPrivate *priv, SoupMessage *msg)
338 {
339         SoupAuthHost *host;
340         const char *path, *realm;
341
342         host = get_auth_host_for_message (priv, msg);
343         if (!host->auth_realms)
344                 return NULL;
345
346         path = soup_message_get_uri (msg)->path;
347         if (!path)
348                 path = "/";
349         realm = soup_path_map_lookup (host->auth_realms, path);
350         if (realm)
351                 return g_hash_table_lookup (host->auths, realm);
352         else
353                 return NULL;
354 }
355
356 static gboolean
357 authenticate_auth (SoupAuthManager *manager, SoupAuth *auth,
358                    SoupMessage *msg, gboolean prior_auth_failed,
359                    gboolean proxy, gboolean can_interact)
360 {
361         SoupAuthManagerPrivate *priv = SOUP_AUTH_MANAGER_GET_PRIVATE (manager);
362         SoupURI *uri;
363
364         if (soup_auth_is_authenticated (auth))
365                 return TRUE;
366
367         if (proxy) {
368                 SoupMessageQueue *queue;
369                 SoupMessageQueueItem *item;
370
371                 queue = soup_session_get_queue (priv->session);
372                 item = soup_message_queue_lookup (queue, msg);
373                 if (item) {
374                         uri = soup_connection_get_proxy_uri (item->conn);
375                         soup_message_queue_item_unref (item);
376                 } else
377                         uri = NULL;
378
379                 if (!uri)
380                         return FALSE;
381         } else
382                 uri = soup_message_get_uri (msg);
383
384         if (uri->password && !prior_auth_failed) {
385                 soup_auth_authenticate (auth, uri->user, uri->password);
386                 return TRUE;
387         }
388
389         if (can_interact) {
390                 soup_auth_manager_emit_authenticate (manager, msg, auth,
391                                                      prior_auth_failed);
392         }
393
394         return soup_auth_is_authenticated (auth);
395 }
396
397 static void
398 update_auth (SoupMessage *msg, gpointer manager)
399 {
400         SoupAuthManagerPrivate *priv = SOUP_AUTH_MANAGER_GET_PRIVATE (manager);
401         SoupAuthHost *host;
402         SoupAuth *auth, *prior_auth, *old_auth;
403         const char *path;
404         char *auth_info, *old_auth_info;
405         GSList *pspace, *p;
406         gboolean prior_auth_failed = FALSE;
407
408         host = get_auth_host_for_message (priv, msg);
409
410         /* See if we used auth last time */
411         prior_auth = soup_message_get_auth (msg);
412         if (prior_auth && check_auth (msg, prior_auth)) {
413                 auth = prior_auth;
414                 if (!soup_auth_is_authenticated (auth))
415                         prior_auth_failed = TRUE;
416         } else {
417                 auth = create_auth (priv, msg);
418                 if (!auth)
419                         return;
420         }
421         auth_info = soup_auth_get_info (auth);
422
423         if (!host->auth_realms) {
424                 host->auth_realms = soup_path_map_new (g_free);
425                 host->auths = g_hash_table_new_full (g_str_hash, g_str_equal,
426                                                      g_free, g_object_unref);
427         }
428
429         /* Record where this auth realm is used. */
430         pspace = soup_auth_get_protection_space (auth, soup_message_get_uri (msg));
431         for (p = pspace; p; p = p->next) {
432                 path = p->data;
433                 old_auth_info = soup_path_map_lookup (host->auth_realms, path);
434                 if (old_auth_info) {
435                         if (!strcmp (old_auth_info, auth_info))
436                                 continue;
437                         soup_path_map_remove (host->auth_realms, path);
438                 }
439
440                 soup_path_map_add (host->auth_realms, path,
441                                    g_strdup (auth_info));
442         }
443         soup_auth_free_protection_space (auth, pspace);
444
445         /* Now, make sure the auth is recorded. (If there's a
446          * pre-existing auth, we keep that rather than the new one,
447          * since the old one might already be authenticated.)
448          */
449         old_auth = g_hash_table_lookup (host->auths, auth_info);
450         if (old_auth) {
451                 g_free (auth_info);
452                 if (auth != old_auth && auth != prior_auth) {
453                         g_object_unref (auth);
454                         auth = old_auth;
455                 }
456         } else {
457                 g_hash_table_insert (host->auths, auth_info, auth);
458         }
459
460         /* If we need to authenticate, try to do it. */
461         authenticate_auth (manager, auth, msg,
462                            prior_auth_failed, FALSE, TRUE);
463 }
464
465 static void
466 requeue_if_authenticated (SoupMessage *msg, gpointer manager)
467 {
468         SoupAuthManagerPrivate *priv = SOUP_AUTH_MANAGER_GET_PRIVATE (manager);
469         SoupAuth *auth = lookup_auth (priv, msg);
470
471         if (auth && soup_auth_is_authenticated (auth))
472                 soup_session_requeue_message (priv->session, msg);
473 }
474
475 static void
476 update_proxy_auth (SoupMessage *msg, gpointer manager)
477 {
478         SoupAuthManagerPrivate *priv = SOUP_AUTH_MANAGER_GET_PRIVATE (manager);
479         SoupAuth *prior_auth;
480         gboolean prior_auth_failed = FALSE;
481
482         /* See if we used auth last time */
483         prior_auth = soup_message_get_proxy_auth (msg);
484         if (prior_auth && check_auth (msg, prior_auth)) {
485                 if (!soup_auth_is_authenticated (prior_auth))
486                         prior_auth_failed = TRUE;
487         }
488
489         if (!priv->proxy_auth) {
490                 priv->proxy_auth = create_auth (priv, msg);
491                 if (!priv->proxy_auth)
492                         return;
493         }
494
495         /* If we need to authenticate, try to do it. */
496         authenticate_auth (manager, priv->proxy_auth, msg,
497                            prior_auth_failed, TRUE, TRUE);
498 }
499
500 static void
501 requeue_if_proxy_authenticated (SoupMessage *msg, gpointer manager)
502 {
503         SoupAuthManagerPrivate *priv = SOUP_AUTH_MANAGER_GET_PRIVATE (manager);
504         SoupAuth *auth = priv->proxy_auth;
505
506         if (auth && soup_auth_is_authenticated (auth))
507                 soup_session_requeue_message (priv->session, msg);
508 }
509
510 static void
511 request_queued (SoupSessionFeature *manager, SoupSession *session,
512                 SoupMessage *msg)
513 {
514         soup_message_add_status_code_handler (
515                 msg, "got_headers", SOUP_STATUS_UNAUTHORIZED,
516                 G_CALLBACK (update_auth), manager);
517         soup_message_add_status_code_handler (
518                 msg, "got_body", SOUP_STATUS_UNAUTHORIZED,
519                 G_CALLBACK (requeue_if_authenticated), manager);
520
521         soup_message_add_status_code_handler (
522                 msg, "got_headers", SOUP_STATUS_PROXY_UNAUTHORIZED,
523                 G_CALLBACK (update_proxy_auth), manager);
524         soup_message_add_status_code_handler (
525                 msg, "got_body", SOUP_STATUS_PROXY_UNAUTHORIZED,
526                 G_CALLBACK (requeue_if_proxy_authenticated), manager);
527 }
528
529 static void
530 request_started (SoupSessionFeature *feature, SoupSession *session,
531                  SoupMessage *msg, SoupSocket *socket)
532 {
533         SoupAuthManager *manager = SOUP_AUTH_MANAGER (feature);
534         SoupAuthManagerPrivate *priv = SOUP_AUTH_MANAGER_GET_PRIVATE (manager);
535         SoupAuth *auth;
536
537         auth = lookup_auth (priv, msg);
538         if (!auth || !authenticate_auth (manager, auth, msg, FALSE, FALSE, FALSE))
539                 auth = NULL;
540         soup_message_set_auth (msg, auth);
541
542         auth = priv->proxy_auth;
543         if (!auth || !authenticate_auth (manager, auth, msg, FALSE, TRUE, FALSE))
544                 auth = NULL;
545         soup_message_set_proxy_auth (msg, auth);
546 }
547
548 static void
549 request_unqueued (SoupSessionFeature *manager, SoupSession *session,
550                   SoupMessage *msg)
551 {
552         g_signal_handlers_disconnect_matched (msg, G_SIGNAL_MATCH_DATA,
553                                               0, 0, NULL, NULL, manager);
554 }