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