/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
/*
* soup-auth-domain-digest.c: HTTP Digest Authentication (server-side)
*
* Copyright (C) 2007 Novell, Inc.
*/
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <string.h>
#include <stdlib.h>
#include "soup-auth-domain-digest.h"
#include "soup.h"
#include "soup-auth-digest.h"
/**
* SECTION:soup-auth-domain-digest
* @short_description: Server-side "Digest" authentication
*
* #SoupAuthDomainDigest handles the server side of HTTP "Digest"
* authentication.
**/
enum {
PROP_0,
PROP_AUTH_CALLBACK,
PROP_AUTH_DATA,
LAST_PROP
};
typedef struct {
SoupAuthDomainDigestAuthCallback auth_callback;
gpointer auth_data;
GDestroyNotify auth_dnotify;
} SoupAuthDomainDigestPrivate;
G_DEFINE_TYPE_WITH_PRIVATE (SoupAuthDomainDigest, soup_auth_domain_digest, SOUP_TYPE_AUTH_DOMAIN)
static void
soup_auth_domain_digest_init (SoupAuthDomainDigest *digest)
{
}
static void
soup_auth_domain_digest_finalize (GObject *object)
{
SoupAuthDomainDigestPrivate *priv =
soup_auth_domain_digest_get_instance_private (SOUP_AUTH_DOMAIN_DIGEST (object));
if (priv->auth_dnotify)
priv->auth_dnotify (priv->auth_data);
G_OBJECT_CLASS (soup_auth_domain_digest_parent_class)->finalize (object);
}
static void
soup_auth_domain_digest_set_property (GObject *object, guint prop_id,
const GValue *value, GParamSpec *pspec)
{
SoupAuthDomainDigestPrivate *priv =
soup_auth_domain_digest_get_instance_private (SOUP_AUTH_DOMAIN_DIGEST (object));
switch (prop_id) {
case PROP_AUTH_CALLBACK:
priv->auth_callback = g_value_get_pointer (value);
break;
case PROP_AUTH_DATA:
if (priv->auth_dnotify) {
priv->auth_dnotify (priv->auth_data);
priv->auth_dnotify = NULL;
}
priv->auth_data = g_value_get_pointer (value);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static void
soup_auth_domain_digest_get_property (GObject *object, guint prop_id,
GValue *value, GParamSpec *pspec)
{
SoupAuthDomainDigestPrivate *priv =
soup_auth_domain_digest_get_instance_private (SOUP_AUTH_DOMAIN_DIGEST (object));
switch (prop_id) {
case PROP_AUTH_CALLBACK:
g_value_set_pointer (value, priv->auth_callback);
break;
case PROP_AUTH_DATA:
g_value_set_pointer (value, priv->auth_data);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
/**
* soup_auth_domain_digest_new:
* @optname1: name of first option, or %NULL
* @...: option name/value pairs
*
* Creates a #SoupAuthDomainDigest. You must set the
* %SOUP_AUTH_DOMAIN_REALM parameter, to indicate the realm name to be
* returned with the authentication challenge to the client. Other
* parameters are optional.
*
* Return value: the new #SoupAuthDomain
**/
SoupAuthDomain *
soup_auth_domain_digest_new (const char *optname1, ...)
{
SoupAuthDomain *domain;
va_list ap;
va_start (ap, optname1);
domain = (SoupAuthDomain *)g_object_new_valist (SOUP_TYPE_AUTH_DOMAIN_DIGEST,
optname1, ap);
va_end (ap);
g_return_val_if_fail (soup_auth_domain_get_realm (domain) != NULL, NULL);
return domain;
}
/**
* SoupAuthDomainDigestAuthCallback:
* @domain: the domain
* @msg: the message being authenticated
* @username: the username provided by the client
* @user_data: the data passed to soup_auth_domain_digest_set_auth_callback()
*
* Callback used by #SoupAuthDomainDigest for authentication purposes.
* The application should look up @username in its password database,
* and return the corresponding encoded password (see
* soup_auth_domain_digest_encode_password()).
*
* Return value: (nullable): the encoded password, or %NULL if
* @username is not a valid user. @domain will free the password when
* it is done with it.
**/
/**
* soup_auth_domain_digest_set_auth_callback:
* @domain: the domain
* @callback: the callback
* @user_data: data to pass to @auth_callback
* @dnotify: destroy notifier to free @user_data when @domain
* is destroyed
*
* Sets the callback that @domain will use to authenticate incoming
* requests. For each request containing authorization, @domain will
* invoke the callback, and then either accept or reject the request
* based on @callback's return value.
*
* You can also set the auth callback by setting the
* %SOUP_AUTH_DOMAIN_DIGEST_AUTH_CALLBACK and
* %SOUP_AUTH_DOMAIN_DIGEST_AUTH_DATA properties, which can also be
* used to set the callback at construct time.
**/
void
soup_auth_domain_digest_set_auth_callback (SoupAuthDomain *domain,
SoupAuthDomainDigestAuthCallback callback,
gpointer user_data,
GDestroyNotify dnotify)
{
SoupAuthDomainDigestPrivate *priv =
soup_auth_domain_digest_get_instance_private (SOUP_AUTH_DOMAIN_DIGEST (domain));
if (priv->auth_dnotify)
priv->auth_dnotify (priv->auth_data);
priv->auth_callback = callback;
priv->auth_data = user_data;
priv->auth_dnotify = dnotify;
g_object_notify (G_OBJECT (domain), SOUP_AUTH_DOMAIN_DIGEST_AUTH_CALLBACK);
g_object_notify (G_OBJECT (domain), SOUP_AUTH_DOMAIN_DIGEST_AUTH_DATA);
}
static gboolean
check_hex_urp (SoupAuthDomain *domain, SoupMessage *msg,
GHashTable *params, const char *username,
const char *hex_urp)
{
const char *uri, *qop, *realm, *msg_username;
const char *nonce, *nc, *cnonce, *response;
char hex_a1[33], computed_response[33];
int nonce_count;
SoupURI *dig_uri, *req_uri;
msg_username = g_hash_table_lookup (params, "username");
if (!msg_username || strcmp (msg_username, username) != 0)
return FALSE;
/* Check uri */
uri = g_hash_table_lookup (params, "uri");
if (!uri)
return FALSE;
req_uri = soup_message_get_uri (msg);
dig_uri = soup_uri_new (uri);
if (dig_uri) {
if (!soup_uri_equal (dig_uri, req_uri)) {
soup_uri_free (dig_uri);
return FALSE;
}
soup_uri_free (dig_uri);
} else {
char *req_path;
char *dig_path;
req_path = soup_uri_to_string (req_uri, TRUE);
dig_path = soup_uri_decode (uri);
if (strcmp (dig_path, req_path) != 0) {
g_free (req_path);
g_free (dig_path);
return FALSE;
}
g_free (req_path);
g_free (dig_path);
}
/* Check qop; we only support "auth" for now */
qop = g_hash_table_lookup (params, "qop");
if (!qop || strcmp (qop, "auth") != 0)
return FALSE;
/* Check realm */
realm = g_hash_table_lookup (params, "realm");
if (!realm || strcmp (realm, soup_auth_domain_get_realm (domain)) != 0)
return FALSE;
nonce = g_hash_table_lookup (params, "nonce");
if (!nonce)
return FALSE;
nc = g_hash_table_lookup (params, "nc");
if (!nc)
return FALSE;
nonce_count = strtoul (nc, NULL, 16);
if (nonce_count <= 0)
return FALSE;
cnonce = g_hash_table_lookup (params, "cnonce");
if (!cnonce)
return FALSE;
response = g_hash_table_lookup (params, "response");
if (!response)
return FALSE;
soup_auth_digest_compute_hex_a1 (hex_urp,
SOUP_AUTH_DIGEST_ALGORITHM_MD5,
nonce, cnonce, hex_a1);
soup_auth_digest_compute_response (msg->method, uri,
hex_a1,
SOUP_AUTH_DIGEST_QOP_AUTH,
nonce, cnonce, nonce_count,
computed_response);
return strcmp (response, computed_response) == 0;
}
static char *
soup_auth_domain_digest_accepts (SoupAuthDomain *domain, SoupMessage *msg,
const char *header)
{
SoupAuthDomainDigestPrivate *priv =
soup_auth_domain_digest_get_instance_private (SOUP_AUTH_DOMAIN_DIGEST (domain));
GHashTable *params;
const char *username;
gboolean accept = FALSE;
char *ret_user;
if (strncmp (header, "Digest ", 7) != 0)
return NULL;
params = soup_header_parse_param_list (header + 7);
if (!params)
return NULL;
username = g_hash_table_lookup (params, "username");
if (!username) {
soup_header_free_param_list (params);
return NULL;
}
if (priv->auth_callback) {
char *hex_urp;
hex_urp = priv->auth_callback (domain, msg, username,
priv->auth_data);
if (hex_urp) {
accept = check_hex_urp (domain, msg, params,
username, hex_urp);
g_free (hex_urp);
} else
accept = FALSE;
} else {
accept = soup_auth_domain_try_generic_auth_callback (
domain, msg, username);
}
ret_user = accept ? g_strdup (username) : NULL;
soup_header_free_param_list (params);
return ret_user;
}
static char *
soup_auth_domain_digest_challenge (SoupAuthDomain *domain, SoupMessage *msg)
{
GString *str;
str = g_string_new ("Digest ");
soup_header_g_string_append_param_quoted (str, "realm", soup_auth_domain_get_realm (domain));
g_string_append_printf (str, ", nonce=\"%lu%lu\"",
(unsigned long) msg,
(unsigned long) time (0));
g_string_append_printf (str, ", qop=\"auth\"");
g_string_append_printf (str, ", algorithm=MD5");
return g_string_free (str, FALSE);
}
/**
* soup_auth_domain_digest_encode_password:
* @username: a username
* @realm: an auth realm name
* @password: the password for @username in @realm
*
* Encodes the username/realm/password triplet for Digest
* authentication. (That is, it returns a stringified MD5 hash of
* @username, @realm, and @password concatenated together). This is
* the form that is needed as the return value of
* #SoupAuthDomainDigest's auth handler.
*
* For security reasons, you should store the encoded hash, rather
* than storing the cleartext password itself and calling this method
* only when you need to verify it. This way, if your server is
* compromised, the attackers will not gain access to cleartext
* passwords which might also be usable at other sites. (Note also
* that the encoded password returned by this method is identical to
* the encoded password stored in an Apache .htdigest file.)
*
* Return value: the encoded password
**/
char *
soup_auth_domain_digest_encode_password (const char *username,
const char *realm,
const char *password)
{
char hex_urp[33];
soup_auth_digest_compute_hex_urp (username, realm, password, hex_urp);
return g_strdup (hex_urp);
}
static gboolean
soup_auth_domain_digest_check_password (SoupAuthDomain *domain,
SoupMessage *msg,
const char *username,
const char *password)
{
const char *header;
GHashTable *params;
const char *msg_username;
char hex_urp[33];
gboolean accept;
header = soup_message_headers_get_one (msg->request_headers,
"Authorization");
if (!header || (strncmp (header, "Digest ", 7) != 0))
return FALSE;
params = soup_header_parse_param_list (header + 7);
if (!params)
return FALSE;
msg_username = g_hash_table_lookup (params, "username");
if (!msg_username || strcmp (msg_username, username) != 0) {
soup_header_free_param_list (params);
return FALSE;
}
soup_auth_digest_compute_hex_urp (username,
soup_auth_domain_get_realm (domain),
password, hex_urp);
accept = check_hex_urp (domain, msg, params, username, hex_urp);
soup_header_free_param_list (params);
return accept;
}
static void
soup_auth_domain_digest_class_init (SoupAuthDomainDigestClass *digest_class)
{
SoupAuthDomainClass *auth_domain_class =
SOUP_AUTH_DOMAIN_CLASS (digest_class);
GObjectClass *object_class = G_OBJECT_CLASS (digest_class);
auth_domain_class->accepts = soup_auth_domain_digest_accepts;
auth_domain_class->challenge = soup_auth_domain_digest_challenge;
auth_domain_class->check_password = soup_auth_domain_digest_check_password;
object_class->finalize = soup_auth_domain_digest_finalize;
object_class->set_property = soup_auth_domain_digest_set_property;
object_class->get_property = soup_auth_domain_digest_get_property;
/**
* SOUP_AUTH_DOMAIN_DIGEST_AUTH_CALLBACK:
*
* Alias for the #SoupAuthDomainDigest:auth-callback property.
* (The #SoupAuthDomainDigestAuthCallback.)
**/
g_object_class_install_property (
object_class, PROP_AUTH_CALLBACK,
g_param_spec_pointer (SOUP_AUTH_DOMAIN_DIGEST_AUTH_CALLBACK,
"Authentication callback",
"Password-finding callback",
G_PARAM_READWRITE));
/**
* SOUP_AUTH_DOMAIN_DIGEST_AUTH_DATA:
*
* Alias for the #SoupAuthDomainDigest:auth-callback property.
* (The #SoupAuthDomainDigestAuthCallback.)
**/
g_object_class_install_property (
object_class, PROP_AUTH_DATA,
g_param_spec_pointer (SOUP_AUTH_DOMAIN_DIGEST_AUTH_DATA,
"Authentication callback data",
"Data to pass to authentication callback",
G_PARAM_READWRITE));
}