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