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