Git init
[profile/ivi/libsoup2.4.git] / libsoup / soup-auth-domain-digest.c
1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
2 /*
3  * soup-auth-domain-digest.c: HTTP Digest Authentication (server-side)
4  *
5  * Copyright (C) 2007 Novell, Inc.
6  */
7
8 #ifdef HAVE_CONFIG_H
9 #include <config.h>
10 #endif
11
12 #include <string.h>
13 #include <stdlib.h>
14
15 #include "soup-auth-domain-digest.h"
16 #include "soup-auth-digest.h"
17 #include "soup-headers.h"
18 #include "soup-marshal.h"
19 #include "soup-message.h"
20 #include "soup-uri.h"
21
22 /**
23  * SECTION:soup-auth-domain-digest
24  * @short_description: Server-side "Digest" authentication
25  *
26  * #SoupAuthDomainBasic handles the server side of HTTP "Digest"
27  * authentication.
28  **/
29
30 enum {
31         PROP_0,
32
33         PROP_AUTH_CALLBACK,
34         PROP_AUTH_DATA,
35
36         LAST_PROP
37 };
38
39 typedef struct {
40         SoupAuthDomainDigestAuthCallback auth_callback;
41         gpointer auth_data;
42         GDestroyNotify auth_dnotify;
43
44 } SoupAuthDomainDigestPrivate;
45
46 #define SOUP_AUTH_DOMAIN_DIGEST_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), SOUP_TYPE_AUTH_DOMAIN_DIGEST, SoupAuthDomainDigestPrivate))
47
48 G_DEFINE_TYPE (SoupAuthDomainDigest, soup_auth_domain_digest, SOUP_TYPE_AUTH_DOMAIN)
49
50 static char    *accepts        (SoupAuthDomain *domain,
51                                 SoupMessage    *msg,
52                                 const char     *header);
53 static char    *challenge      (SoupAuthDomain *domain,
54                                 SoupMessage    *msg);
55 static gboolean check_password (SoupAuthDomain *domain,
56                                 SoupMessage    *msg,
57                                 const char     *username,
58                                 const char     *password);
59
60 static void set_property (GObject *object, guint prop_id,
61                           const GValue *value, GParamSpec *pspec);
62 static void get_property (GObject *object, guint prop_id,
63                           GValue *value, GParamSpec *pspec);
64
65 static void
66 soup_auth_domain_digest_init (SoupAuthDomainDigest *digest)
67 {
68 }
69
70 static void
71 finalize (GObject *object)
72 {
73         SoupAuthDomainDigestPrivate *priv =
74                 SOUP_AUTH_DOMAIN_DIGEST_GET_PRIVATE (object);
75
76         if (priv->auth_dnotify)
77                 priv->auth_dnotify (priv->auth_data);
78
79         G_OBJECT_CLASS (soup_auth_domain_digest_parent_class)->finalize (object);
80 }
81
82 static void
83 soup_auth_domain_digest_class_init (SoupAuthDomainDigestClass *digest_class)
84 {
85         SoupAuthDomainClass *auth_domain_class =
86                 SOUP_AUTH_DOMAIN_CLASS (digest_class);
87         GObjectClass *object_class = G_OBJECT_CLASS (digest_class);
88
89         g_type_class_add_private (digest_class, sizeof (SoupAuthDomainDigestPrivate));
90
91         auth_domain_class->accepts        = accepts;
92         auth_domain_class->challenge      = challenge;
93         auth_domain_class->check_password = check_password;
94
95         object_class->finalize     = finalize;
96         object_class->set_property = set_property;
97         object_class->get_property = get_property;
98
99         /**
100          * SOUP_AUTH_DOMAIN_DIGEST_AUTH_CALLBACK:
101          *
102          * Alias for the #SoupAuthDomainDigest:auth-callback property.
103          * (The #SoupAuthDomainDigestAuthCallback.)
104          **/
105         g_object_class_install_property (
106                 object_class, PROP_AUTH_CALLBACK,
107                 g_param_spec_pointer (SOUP_AUTH_DOMAIN_DIGEST_AUTH_CALLBACK,
108                                       "Authentication callback",
109                                       "Password-finding callback",
110                                       G_PARAM_READWRITE));
111         /**
112          * SOUP_AUTH_DOMAIN_DIGEST_AUTH_DATA:
113          *
114          * Alias for the #SoupAuthDomainDigest:auth-callback property.
115          * (The #SoupAuthDomainDigestAuthCallback.)
116          **/
117         g_object_class_install_property (
118                 object_class, PROP_AUTH_DATA,
119                 g_param_spec_pointer (SOUP_AUTH_DOMAIN_DIGEST_AUTH_DATA,
120                                       "Authentication callback data",
121                                       "Data to pass to authentication callback",
122                                       G_PARAM_READWRITE));
123 }
124
125 static void
126 set_property (GObject *object, guint prop_id,
127               const GValue *value, GParamSpec *pspec)
128 {
129         SoupAuthDomainDigestPrivate *priv =
130                 SOUP_AUTH_DOMAIN_DIGEST_GET_PRIVATE (object);
131
132         switch (prop_id) {
133         case PROP_AUTH_CALLBACK:
134                 priv->auth_callback = g_value_get_pointer (value);
135                 break;
136         case PROP_AUTH_DATA:
137                 if (priv->auth_dnotify) {
138                         priv->auth_dnotify (priv->auth_data);
139                         priv->auth_dnotify = NULL;
140                 }
141                 priv->auth_data = g_value_get_pointer (value);
142                 break;
143         default:
144                 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
145                 break;
146         }
147 }
148
149 static void
150 get_property (GObject *object, guint prop_id,
151               GValue *value, GParamSpec *pspec)
152 {
153         SoupAuthDomainDigestPrivate *priv =
154                 SOUP_AUTH_DOMAIN_DIGEST_GET_PRIVATE (object);
155
156         switch (prop_id) {
157         case PROP_AUTH_CALLBACK:
158                 g_value_set_pointer (value, priv->auth_callback);
159                 break;
160         case PROP_AUTH_DATA:
161                 g_value_set_pointer (value, priv->auth_data);
162                 break;
163         default:
164                 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
165                 break;
166         }
167 }
168
169 /**
170  * soup_auth_domain_digest_new:
171  * @optname1: name of first option, or %NULL
172  * @...: option name/value pairs
173  *
174  * Creates a #SoupAuthDomainDigest. You must set the
175  * %SOUP_AUTH_DOMAIN_REALM parameter, to indicate the realm name to be
176  * returned with the authentication challenge to the client. Other
177  * parameters are optional.
178  *
179  * Return value: the new #SoupAuthDomain
180  **/
181 SoupAuthDomain *
182 soup_auth_domain_digest_new (const char *optname1, ...)
183 {
184         SoupAuthDomain *domain;
185         va_list ap;
186
187         va_start (ap, optname1);
188         domain = (SoupAuthDomain *)g_object_new_valist (SOUP_TYPE_AUTH_DOMAIN_DIGEST,
189                                                         optname1, ap);
190         va_end (ap);
191
192         g_return_val_if_fail (soup_auth_domain_get_realm (domain) != NULL, NULL);
193
194         return domain;
195 }
196
197 /**
198  * SoupAuthDomainDigestAuthCallback:
199  * @domain: the domain
200  * @msg: the message being authenticated
201  * @username: the username provided by the client
202  * @user_data: the data passed to soup_auth_domain_digest_set_auth_callback()
203  *
204  * Callback used by #SoupAuthDomainDigest for authentication purposes.
205  * The application should look up @username in its password database,
206  * and return the corresponding encoded password (see
207  * soup_auth_domain_digest_encode_password()).
208  *
209  * Return value: the encoded password, or %NULL if @username is not a
210  * valid user. @domain will free the password when it is done with it.
211  **/
212
213 /**
214  * soup_auth_domain_digest_set_auth_callback:
215  * @domain: the domain
216  * @callback: the callback
217  * @user_data: data to pass to @auth_callback
218  * @dnotify: destroy notifier to free @user_data when @domain
219  * is destroyed
220  *
221  * Sets the callback that @domain will use to authenticate incoming
222  * requests. For each request containing authorization, @domain will
223  * invoke the callback, and then either accept or reject the request
224  * based on @callback's return value.
225  *
226  * You can also set the auth callback by setting the
227  * %SOUP_AUTH_DOMAIN_DIGEST_AUTH_CALLBACK and
228  * %SOUP_AUTH_DOMAIN_DIGEST_AUTH_DATA properties, which can also be
229  * used to set the callback at construct time.
230  **/
231 void
232 soup_auth_domain_digest_set_auth_callback (SoupAuthDomain *domain,
233                                            SoupAuthDomainDigestAuthCallback callback,
234                                            gpointer        user_data,
235                                            GDestroyNotify  dnotify)
236 {
237         SoupAuthDomainDigestPrivate *priv =
238                 SOUP_AUTH_DOMAIN_DIGEST_GET_PRIVATE (domain);
239
240         if (priv->auth_dnotify)
241                 priv->auth_dnotify (priv->auth_data);
242
243         priv->auth_callback = callback;
244         priv->auth_data = user_data;
245         priv->auth_dnotify = dnotify;
246
247         g_object_notify (G_OBJECT (domain), SOUP_AUTH_DOMAIN_DIGEST_AUTH_CALLBACK);
248         g_object_notify (G_OBJECT (domain), SOUP_AUTH_DOMAIN_DIGEST_AUTH_DATA);
249 }
250
251 static gboolean
252 check_hex_urp (SoupAuthDomain *domain, SoupMessage *msg,
253                GHashTable *params, const char *username,
254                const char *hex_urp)
255 {
256         const char *uri, *qop, *realm, *msg_username;
257         const char *nonce, *nc, *cnonce, *response;
258         char hex_a1[33], computed_response[33];
259         int nonce_count;
260         SoupURI *dig_uri, *req_uri;
261
262         msg_username = g_hash_table_lookup (params, "username");
263         if (!msg_username || strcmp (msg_username, username) != 0)
264                 return FALSE;
265
266         /* Check uri */
267         uri = g_hash_table_lookup (params, "uri");
268         if (!uri)
269                 return FALSE;
270
271         req_uri = soup_message_get_uri (msg);
272         dig_uri = soup_uri_new (uri);
273         if (dig_uri) {
274                 if (!soup_uri_equal (dig_uri, req_uri)) {
275                         soup_uri_free (dig_uri);
276                         return FALSE;
277                 }
278                 soup_uri_free (dig_uri);
279         } else {        
280                 char *req_path;
281
282                 req_path = soup_uri_to_string (req_uri, TRUE);
283                 if (strcmp (uri, req_path) != 0) {
284                         g_free (req_path);
285                         return FALSE;
286                 }
287                 g_free (req_path);
288         }
289
290         /* Check qop; we only support "auth" for now */
291         qop = g_hash_table_lookup (params, "qop");
292         if (!qop || strcmp (qop, "auth") != 0)
293                 return FALSE;
294
295         /* Check realm */
296         realm = g_hash_table_lookup (params, "realm");
297         if (!realm || strcmp (realm, soup_auth_domain_get_realm (domain)) != 0)
298                 return FALSE;
299
300         nonce = g_hash_table_lookup (params, "nonce");
301         if (!nonce)
302                 return FALSE;
303         nc = g_hash_table_lookup (params, "nc");
304         if (!nc)
305                 return FALSE;
306         nonce_count = strtoul (nc, NULL, 16);
307         if (nonce_count <= 0)
308                 return FALSE;
309         cnonce = g_hash_table_lookup (params, "cnonce");
310         if (!cnonce)
311                 return FALSE;
312         response = g_hash_table_lookup (params, "response");
313         if (!response)
314                 return FALSE;
315
316         soup_auth_digest_compute_hex_a1 (hex_urp,
317                                          SOUP_AUTH_DIGEST_ALGORITHM_MD5,
318                                          nonce, cnonce, hex_a1);
319         soup_auth_digest_compute_response (msg->method, uri,
320                                            hex_a1,
321                                            SOUP_AUTH_DIGEST_QOP_AUTH,
322                                            nonce, cnonce, nonce_count,
323                                            computed_response);
324         return strcmp (response, computed_response) == 0;
325 }
326
327 static char *
328 accepts (SoupAuthDomain *domain, SoupMessage *msg, const char *header)
329 {
330         SoupAuthDomainDigestPrivate *priv =
331                 SOUP_AUTH_DOMAIN_DIGEST_GET_PRIVATE (domain);
332         GHashTable *params;
333         const char *username;
334         gboolean accept = FALSE;
335         char *ret_user;
336
337         if (strncmp (header, "Digest ", 7) != 0)
338                 return NULL;
339
340         params = soup_header_parse_param_list (header + 7);
341         if (!params)
342                 return NULL;
343
344         username = g_hash_table_lookup (params, "username");
345         if (!username) {
346                 soup_header_free_param_list (params);
347                 return NULL;
348         }
349
350         if (priv->auth_callback) {
351                 char *hex_urp;
352
353                 hex_urp = priv->auth_callback (domain, msg, username,
354                                                priv->auth_data);
355                 if (hex_urp) {
356                         accept = check_hex_urp (domain, msg, params,
357                                                 username, hex_urp);
358                         g_free (hex_urp);
359                 } else
360                         accept = FALSE;
361         } else {
362                 accept = soup_auth_domain_try_generic_auth_callback (
363                         domain, msg, username);
364         }
365
366         ret_user = accept ? g_strdup (username) : NULL;
367         soup_header_free_param_list (params);
368         return ret_user;
369 }
370
371 static char *
372 challenge (SoupAuthDomain *domain, SoupMessage *msg)
373 {
374         GString *str;
375
376         str = g_string_new ("Digest ");
377         soup_header_g_string_append_param_quoted (str, "realm", soup_auth_domain_get_realm (domain));
378         g_string_append_printf (str, ", nonce=\"%lu%lu\"", 
379                                 (unsigned long) msg,
380                                 (unsigned long) time (0));
381         g_string_append_printf (str, ", qop=\"auth\"");
382         g_string_append_printf (str, ", algorithm=MD5");
383
384         return g_string_free (str, FALSE);
385 }
386
387 /**
388  * soup_auth_domain_digest_encode_password:
389  * @username: a username
390  * @realm: an auth realm name
391  * @password: the password for @username in @realm
392  *
393  * Encodes the username/realm/password triplet for Digest
394  * authentication. (That is, it returns a stringified MD5 hash of
395  * @username, @realm, and @password concatenated together). This is
396  * the form that is needed as the return value of
397  * #SoupAuthDomainDigest's auth handler.
398  *
399  * For security reasons, you should store the encoded hash, rather
400  * than storing the cleartext password itself and calling this method
401  * only when you need to verify it. This way, if your server is
402  * compromised, the attackers will not gain access to cleartext
403  * passwords which might also be usable at other sites. (Note also
404  * that the encoded password returned by this method is identical to
405  * the encoded password stored in an Apache .htdigest file.)
406  *
407  * Return value: the encoded password
408  **/
409 char *
410 soup_auth_domain_digest_encode_password (const char *username,
411                                          const char *realm,
412                                          const char *password)
413 {
414         char hex_urp[33];
415
416         soup_auth_digest_compute_hex_urp (username, realm, password, hex_urp);
417         return g_strdup (hex_urp);
418 }
419
420 static gboolean
421 check_password (SoupAuthDomain *domain,
422                 SoupMessage    *msg,
423                 const char     *username,
424                 const char     *password)
425 {
426         const char *header;
427         GHashTable *params;
428         const char *msg_username;
429         char hex_urp[33];
430         gboolean accept;
431
432         header = soup_message_headers_get_one (msg->request_headers,
433                                                "Authorization");
434         if (strncmp (header, "Digest ", 7) != 0)
435                 return FALSE;
436
437         params = soup_header_parse_param_list (header + 7);
438         if (!params)
439                 return FALSE;
440
441         msg_username = g_hash_table_lookup (params, "username");
442         if (!msg_username || strcmp (msg_username, username) != 0) {
443                 soup_header_free_param_list (params);
444                 return FALSE;
445         }
446
447         soup_auth_digest_compute_hex_urp (username,
448                                           soup_auth_domain_get_realm (domain),
449                                           password, hex_urp);
450         accept = check_hex_urp (domain, msg, params, username, hex_urp);
451         soup_header_free_param_list (params);
452         return accept;
453 }