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