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