1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
3 * soup-auth-domain-digest.c: HTTP Digest Authentication (server-side)
5 * Copyright (C) 2007 Novell, Inc.
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"
23 * SECTION:soup-auth-domain-digest
24 * @short_description: Server-side "Digest" authentication
26 * #SoupAuthDomainBasic handles the server side of HTTP "Digest"
40 SoupAuthDomainDigestAuthCallback auth_callback;
42 GDestroyNotify auth_dnotify;
44 } SoupAuthDomainDigestPrivate;
46 #define SOUP_AUTH_DOMAIN_DIGEST_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), SOUP_TYPE_AUTH_DOMAIN_DIGEST, SoupAuthDomainDigestPrivate))
48 G_DEFINE_TYPE (SoupAuthDomainDigest, soup_auth_domain_digest, SOUP_TYPE_AUTH_DOMAIN)
50 static char *accepts (SoupAuthDomain *domain,
53 static char *challenge (SoupAuthDomain *domain,
55 static gboolean check_password (SoupAuthDomain *domain,
58 const char *password);
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);
66 soup_auth_domain_digest_init (SoupAuthDomainDigest *digest)
71 finalize (GObject *object)
73 SoupAuthDomainDigestPrivate *priv =
74 SOUP_AUTH_DOMAIN_DIGEST_GET_PRIVATE (object);
76 if (priv->auth_dnotify)
77 priv->auth_dnotify (priv->auth_data);
79 G_OBJECT_CLASS (soup_auth_domain_digest_parent_class)->finalize (object);
83 soup_auth_domain_digest_class_init (SoupAuthDomainDigestClass *digest_class)
85 SoupAuthDomainClass *auth_domain_class =
86 SOUP_AUTH_DOMAIN_CLASS (digest_class);
87 GObjectClass *object_class = G_OBJECT_CLASS (digest_class);
89 g_type_class_add_private (digest_class, sizeof (SoupAuthDomainDigestPrivate));
91 auth_domain_class->accepts = accepts;
92 auth_domain_class->challenge = challenge;
93 auth_domain_class->check_password = check_password;
95 object_class->finalize = finalize;
96 object_class->set_property = set_property;
97 object_class->get_property = get_property;
100 * SOUP_AUTH_DOMAIN_DIGEST_AUTH_CALLBACK:
102 * Alias for the #SoupAuthDomainDigest:auth-callback property.
103 * (The #SoupAuthDomainDigestAuthCallback.)
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",
112 * SOUP_AUTH_DOMAIN_DIGEST_AUTH_DATA:
114 * Alias for the #SoupAuthDomainDigest:auth-callback property.
115 * (The #SoupAuthDomainDigestAuthCallback.)
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",
126 set_property (GObject *object, guint prop_id,
127 const GValue *value, GParamSpec *pspec)
129 SoupAuthDomainDigestPrivate *priv =
130 SOUP_AUTH_DOMAIN_DIGEST_GET_PRIVATE (object);
133 case PROP_AUTH_CALLBACK:
134 priv->auth_callback = g_value_get_pointer (value);
137 if (priv->auth_dnotify) {
138 priv->auth_dnotify (priv->auth_data);
139 priv->auth_dnotify = NULL;
141 priv->auth_data = g_value_get_pointer (value);
144 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
150 get_property (GObject *object, guint prop_id,
151 GValue *value, GParamSpec *pspec)
153 SoupAuthDomainDigestPrivate *priv =
154 SOUP_AUTH_DOMAIN_DIGEST_GET_PRIVATE (object);
157 case PROP_AUTH_CALLBACK:
158 g_value_set_pointer (value, priv->auth_callback);
161 g_value_set_pointer (value, priv->auth_data);
164 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
170 * soup_auth_domain_digest_new:
171 * @optname1: name of first option, or %NULL
172 * @...: option name/value pairs
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.
179 * Return value: the new #SoupAuthDomain
182 soup_auth_domain_digest_new (const char *optname1, ...)
184 SoupAuthDomain *domain;
187 va_start (ap, optname1);
188 domain = (SoupAuthDomain *)g_object_new_valist (SOUP_TYPE_AUTH_DOMAIN_DIGEST,
192 g_return_val_if_fail (soup_auth_domain_get_realm (domain) != NULL, NULL);
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()
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()).
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.
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
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.
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.
232 soup_auth_domain_digest_set_auth_callback (SoupAuthDomain *domain,
233 SoupAuthDomainDigestAuthCallback callback,
235 GDestroyNotify dnotify)
237 SoupAuthDomainDigestPrivate *priv =
238 SOUP_AUTH_DOMAIN_DIGEST_GET_PRIVATE (domain);
240 if (priv->auth_dnotify)
241 priv->auth_dnotify (priv->auth_data);
243 priv->auth_callback = callback;
244 priv->auth_data = user_data;
245 priv->auth_dnotify = dnotify;
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);
252 check_hex_urp (SoupAuthDomain *domain, SoupMessage *msg,
253 GHashTable *params, const char *username,
256 const char *uri, *qop, *realm, *msg_username;
257 const char *nonce, *nc, *cnonce, *response;
258 char hex_a1[33], computed_response[33];
260 SoupURI *dig_uri, *req_uri;
262 msg_username = g_hash_table_lookup (params, "username");
263 if (!msg_username || strcmp (msg_username, username) != 0)
267 uri = g_hash_table_lookup (params, "uri");
271 req_uri = soup_message_get_uri (msg);
272 dig_uri = soup_uri_new (uri);
274 if (!soup_uri_equal (dig_uri, req_uri)) {
275 soup_uri_free (dig_uri);
278 soup_uri_free (dig_uri);
282 req_path = soup_uri_to_string (req_uri, TRUE);
283 if (strcmp (uri, req_path) != 0) {
290 /* Check qop; we only support "auth" for now */
291 qop = g_hash_table_lookup (params, "qop");
292 if (!qop || strcmp (qop, "auth") != 0)
296 realm = g_hash_table_lookup (params, "realm");
297 if (!realm || strcmp (realm, soup_auth_domain_get_realm (domain)) != 0)
300 nonce = g_hash_table_lookup (params, "nonce");
303 nc = g_hash_table_lookup (params, "nc");
306 nonce_count = strtoul (nc, NULL, 16);
307 if (nonce_count <= 0)
309 cnonce = g_hash_table_lookup (params, "cnonce");
312 response = g_hash_table_lookup (params, "response");
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,
321 SOUP_AUTH_DIGEST_QOP_AUTH,
322 nonce, cnonce, nonce_count,
324 return strcmp (response, computed_response) == 0;
328 accepts (SoupAuthDomain *domain, SoupMessage *msg, const char *header)
330 SoupAuthDomainDigestPrivate *priv =
331 SOUP_AUTH_DOMAIN_DIGEST_GET_PRIVATE (domain);
333 const char *username;
334 gboolean accept = FALSE;
337 if (strncmp (header, "Digest ", 7) != 0)
340 params = soup_header_parse_param_list (header + 7);
344 username = g_hash_table_lookup (params, "username");
346 soup_header_free_param_list (params);
350 if (priv->auth_callback) {
353 hex_urp = priv->auth_callback (domain, msg, username,
356 accept = check_hex_urp (domain, msg, params,
362 accept = soup_auth_domain_try_generic_auth_callback (
363 domain, msg, username);
366 ret_user = accept ? g_strdup (username) : NULL;
367 soup_header_free_param_list (params);
372 challenge (SoupAuthDomain *domain, SoupMessage *msg)
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\"",
380 (unsigned long) time (0));
381 g_string_append_printf (str, ", qop=\"auth\"");
382 g_string_append_printf (str, ", algorithm=MD5");
384 return g_string_free (str, FALSE);
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
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.
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.)
407 * Return value: the encoded password
410 soup_auth_domain_digest_encode_password (const char *username,
412 const char *password)
416 soup_auth_digest_compute_hex_urp (username, realm, password, hex_urp);
417 return g_strdup (hex_urp);
421 check_password (SoupAuthDomain *domain,
423 const char *username,
424 const char *password)
428 const char *msg_username;
432 header = soup_message_headers_get_one (msg->request_headers,
434 if (!header || (strncmp (header, "Digest ", 7) != 0))
437 params = soup_header_parse_param_list (header + 7);
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);
447 soup_auth_digest_compute_hex_urp (username,
448 soup_auth_domain_get_realm (domain),
450 accept = check_hex_urp (domain, msg, params, username, hex_urp);
451 soup_header_free_param_list (params);