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"
17 #include "soup-auth-digest.h"
20 * SECTION:soup-auth-domain-digest
21 * @short_description: Server-side "Digest" authentication
23 * #SoupAuthDomainDigest handles the server side of HTTP "Digest"
37 SoupAuthDomainDigestAuthCallback auth_callback;
39 GDestroyNotify auth_dnotify;
41 } SoupAuthDomainDigestPrivate;
43 #define SOUP_AUTH_DOMAIN_DIGEST_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), SOUP_TYPE_AUTH_DOMAIN_DIGEST, SoupAuthDomainDigestPrivate))
45 G_DEFINE_TYPE (SoupAuthDomainDigest, soup_auth_domain_digest, SOUP_TYPE_AUTH_DOMAIN)
48 soup_auth_domain_digest_init (SoupAuthDomainDigest *digest)
53 soup_auth_domain_digest_finalize (GObject *object)
55 SoupAuthDomainDigestPrivate *priv =
56 SOUP_AUTH_DOMAIN_DIGEST_GET_PRIVATE (object);
58 if (priv->auth_dnotify)
59 priv->auth_dnotify (priv->auth_data);
61 G_OBJECT_CLASS (soup_auth_domain_digest_parent_class)->finalize (object);
65 soup_auth_domain_digest_set_property (GObject *object, guint prop_id,
66 const GValue *value, GParamSpec *pspec)
68 SoupAuthDomainDigestPrivate *priv =
69 SOUP_AUTH_DOMAIN_DIGEST_GET_PRIVATE (object);
72 case PROP_AUTH_CALLBACK:
73 priv->auth_callback = g_value_get_pointer (value);
76 if (priv->auth_dnotify) {
77 priv->auth_dnotify (priv->auth_data);
78 priv->auth_dnotify = NULL;
80 priv->auth_data = g_value_get_pointer (value);
83 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
89 soup_auth_domain_digest_get_property (GObject *object, guint prop_id,
90 GValue *value, GParamSpec *pspec)
92 SoupAuthDomainDigestPrivate *priv =
93 SOUP_AUTH_DOMAIN_DIGEST_GET_PRIVATE (object);
96 case PROP_AUTH_CALLBACK:
97 g_value_set_pointer (value, priv->auth_callback);
100 g_value_set_pointer (value, priv->auth_data);
103 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
109 * soup_auth_domain_digest_new:
110 * @optname1: name of first option, or %NULL
111 * @...: option name/value pairs
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.
118 * Return value: the new #SoupAuthDomain
121 soup_auth_domain_digest_new (const char *optname1, ...)
123 SoupAuthDomain *domain;
126 va_start (ap, optname1);
127 domain = (SoupAuthDomain *)g_object_new_valist (SOUP_TYPE_AUTH_DOMAIN_DIGEST,
131 g_return_val_if_fail (soup_auth_domain_get_realm (domain) != NULL, NULL);
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()
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()).
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.
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
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.
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.
171 soup_auth_domain_digest_set_auth_callback (SoupAuthDomain *domain,
172 SoupAuthDomainDigestAuthCallback callback,
174 GDestroyNotify dnotify)
176 SoupAuthDomainDigestPrivate *priv =
177 SOUP_AUTH_DOMAIN_DIGEST_GET_PRIVATE (domain);
179 if (priv->auth_dnotify)
180 priv->auth_dnotify (priv->auth_data);
182 priv->auth_callback = callback;
183 priv->auth_data = user_data;
184 priv->auth_dnotify = dnotify;
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);
191 check_hex_urp (SoupAuthDomain *domain, SoupMessage *msg,
192 GHashTable *params, const char *username,
195 const char *uri, *qop, *realm, *msg_username;
196 const char *nonce, *nc, *cnonce, *response;
197 char hex_a1[33], computed_response[33];
199 SoupURI *dig_uri, *req_uri;
201 msg_username = g_hash_table_lookup (params, "username");
202 if (!msg_username || strcmp (msg_username, username) != 0)
206 uri = g_hash_table_lookup (params, "uri");
210 req_uri = soup_message_get_uri (msg);
211 dig_uri = soup_uri_new (uri);
213 if (!soup_uri_equal (dig_uri, req_uri)) {
214 soup_uri_free (dig_uri);
217 soup_uri_free (dig_uri);
221 req_path = soup_uri_to_string (req_uri, TRUE);
222 if (strcmp (uri, req_path) != 0) {
229 /* Check qop; we only support "auth" for now */
230 qop = g_hash_table_lookup (params, "qop");
231 if (!qop || strcmp (qop, "auth") != 0)
235 realm = g_hash_table_lookup (params, "realm");
236 if (!realm || strcmp (realm, soup_auth_domain_get_realm (domain)) != 0)
239 nonce = g_hash_table_lookup (params, "nonce");
242 nc = g_hash_table_lookup (params, "nc");
245 nonce_count = strtoul (nc, NULL, 16);
246 if (nonce_count <= 0)
248 cnonce = g_hash_table_lookup (params, "cnonce");
251 response = g_hash_table_lookup (params, "response");
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,
260 SOUP_AUTH_DIGEST_QOP_AUTH,
261 nonce, cnonce, nonce_count,
263 return strcmp (response, computed_response) == 0;
267 soup_auth_domain_digest_accepts (SoupAuthDomain *domain, SoupMessage *msg,
270 SoupAuthDomainDigestPrivate *priv =
271 SOUP_AUTH_DOMAIN_DIGEST_GET_PRIVATE (domain);
273 const char *username;
274 gboolean accept = FALSE;
277 if (strncmp (header, "Digest ", 7) != 0)
280 params = soup_header_parse_param_list (header + 7);
284 username = g_hash_table_lookup (params, "username");
286 soup_header_free_param_list (params);
290 if (priv->auth_callback) {
293 hex_urp = priv->auth_callback (domain, msg, username,
296 accept = check_hex_urp (domain, msg, params,
302 accept = soup_auth_domain_try_generic_auth_callback (
303 domain, msg, username);
306 ret_user = accept ? g_strdup (username) : NULL;
307 soup_header_free_param_list (params);
312 soup_auth_domain_digest_challenge (SoupAuthDomain *domain, SoupMessage *msg)
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\"",
320 (unsigned long) time (0));
321 g_string_append_printf (str, ", qop=\"auth\"");
322 g_string_append_printf (str, ", algorithm=MD5");
324 return g_string_free (str, FALSE);
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
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.
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.)
347 * Return value: the encoded password
350 soup_auth_domain_digest_encode_password (const char *username,
352 const char *password)
356 soup_auth_digest_compute_hex_urp (username, realm, password, hex_urp);
357 return g_strdup (hex_urp);
361 soup_auth_domain_digest_check_password (SoupAuthDomain *domain,
363 const char *username,
364 const char *password)
368 const char *msg_username;
372 header = soup_message_headers_get_one (msg->request_headers,
374 if (!header || (strncmp (header, "Digest ", 7) != 0))
377 params = soup_header_parse_param_list (header + 7);
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);
387 soup_auth_digest_compute_hex_urp (username,
388 soup_auth_domain_get_realm (domain),
390 accept = check_hex_urp (domain, msg, params, username, hex_urp);
391 soup_header_free_param_list (params);
396 soup_auth_domain_digest_class_init (SoupAuthDomainDigestClass *digest_class)
398 SoupAuthDomainClass *auth_domain_class =
399 SOUP_AUTH_DOMAIN_CLASS (digest_class);
400 GObjectClass *object_class = G_OBJECT_CLASS (digest_class);
402 g_type_class_add_private (digest_class, sizeof (SoupAuthDomainDigestPrivate));
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;
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;
413 * SOUP_AUTH_DOMAIN_DIGEST_AUTH_CALLBACK:
415 * Alias for the #SoupAuthDomainDigest:auth-callback property.
416 * (The #SoupAuthDomainDigestAuthCallback.)
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",
425 * SOUP_AUTH_DOMAIN_DIGEST_AUTH_DATA:
427 * Alias for the #SoupAuthDomainDigest:auth-callback property.
428 * (The #SoupAuthDomainDigestAuthCallback.)
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",