937a200089
This is actually a fully functional build except: * All internal shared libraries are static linked to make sure there is no interference with ports (and to reduce build time). * It does not have the python/perl/etc plugin or API support. * By default, it installs as "svnlite" rather than "svn". * If WITH_SVN added in make.conf, you get "svn". * If WITHOUT_SVNLITE is in make.conf, this is completely disabled. To be absolutely clear, this is not intended for any use other than checking out freebsd source and committing, like we once did with cvs. It should be usable for small scale local repositories that don't need the python/perl plugin architecture.
487 lines
15 KiB
C
487 lines
15 KiB
C
/* Copyright 2009 Justin Erenkrantz and Greg Stein
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
|
|
/*** Digest authentication ***/
|
|
|
|
#include <serf.h>
|
|
#include <serf_private.h>
|
|
#include <auth/auth.h>
|
|
|
|
#include <apr.h>
|
|
#include <apr_base64.h>
|
|
#include <apr_strings.h>
|
|
#include <apr_uuid.h>
|
|
#include <apr_md5.h>
|
|
|
|
/** Digest authentication, implements RFC 2617. **/
|
|
|
|
/* Stores the context information related to Digest authentication.
|
|
The context is per connection. */
|
|
typedef struct digest_authn_info_t {
|
|
/* nonce-count for digest authentication */
|
|
unsigned int digest_nc;
|
|
|
|
const char *header;
|
|
|
|
const char *ha1;
|
|
|
|
const char *realm;
|
|
const char *cnonce;
|
|
const char *nonce;
|
|
const char *opaque;
|
|
const char *algorithm;
|
|
const char *qop;
|
|
const char *username;
|
|
|
|
apr_pool_t *pool;
|
|
} digest_authn_info_t;
|
|
|
|
static char
|
|
int_to_hex(int v)
|
|
{
|
|
return (v < 10) ? '0' + v : 'a' + (v - 10);
|
|
}
|
|
|
|
/**
|
|
* Convert a string if ASCII characters HASHVAL to its hexadecimal
|
|
* representation.
|
|
*
|
|
* The returned string will be allocated in the POOL and be null-terminated.
|
|
*/
|
|
static const char *
|
|
hex_encode(const unsigned char *hashval,
|
|
apr_pool_t *pool)
|
|
{
|
|
int i;
|
|
char *hexval = apr_palloc(pool, (APR_MD5_DIGESTSIZE * 2) + 1);
|
|
for (i = 0; i < APR_MD5_DIGESTSIZE; i++) {
|
|
hexval[2 * i] = int_to_hex((hashval[i] >> 4) & 0xf);
|
|
hexval[2 * i + 1] = int_to_hex(hashval[i] & 0xf);
|
|
}
|
|
hexval[APR_MD5_DIGESTSIZE * 2] = '\0';
|
|
return hexval;
|
|
}
|
|
|
|
/**
|
|
* Returns a 36-byte long string of random characters.
|
|
* UUIDs are formatted as: 00112233-4455-6677-8899-AABBCCDDEEFF.
|
|
*
|
|
* The returned string will be allocated in the POOL and be null-terminated.
|
|
*/
|
|
static const char *
|
|
random_cnonce(apr_pool_t *pool)
|
|
{
|
|
apr_uuid_t uuid;
|
|
char *buf = apr_palloc(pool, APR_UUID_FORMATTED_LENGTH + 1);
|
|
|
|
apr_uuid_get(&uuid);
|
|
apr_uuid_format(buf, &uuid);
|
|
|
|
return hex_encode((unsigned char*)buf, pool);
|
|
}
|
|
|
|
static const char *
|
|
build_digest_ha1(const char *username,
|
|
const char *password,
|
|
const char *realm_name,
|
|
apr_pool_t *pool)
|
|
{
|
|
const char *tmp;
|
|
unsigned char ha1[APR_MD5_DIGESTSIZE];
|
|
apr_status_t status;
|
|
|
|
/* calculate ha1:
|
|
MD5 hash of the combined user name, authentication realm and password */
|
|
tmp = apr_psprintf(pool, "%s:%s:%s",
|
|
username,
|
|
realm_name,
|
|
password);
|
|
status = apr_md5(ha1, tmp, strlen(tmp));
|
|
|
|
return hex_encode(ha1, pool);
|
|
}
|
|
|
|
static const char *
|
|
build_digest_ha2(const char *uri,
|
|
const char *method,
|
|
const char *qop,
|
|
apr_pool_t *pool)
|
|
{
|
|
if (!qop || strcmp(qop, "auth") == 0) {
|
|
const char *tmp;
|
|
unsigned char ha2[APR_MD5_DIGESTSIZE];
|
|
apr_status_t status;
|
|
|
|
/* calculate ha2:
|
|
MD5 hash of the combined method and URI */
|
|
tmp = apr_psprintf(pool, "%s:%s",
|
|
method,
|
|
uri);
|
|
status = apr_md5(ha2, tmp, strlen(tmp));
|
|
|
|
return hex_encode(ha2, pool);
|
|
} else {
|
|
/* TODO: auth-int isn't supported! */
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static const char *
|
|
build_auth_header(digest_authn_info_t *digest_info,
|
|
const char *path,
|
|
const char *method,
|
|
apr_pool_t *pool)
|
|
{
|
|
char *hdr;
|
|
const char *ha2;
|
|
const char *response;
|
|
unsigned char response_hdr[APR_MD5_DIGESTSIZE];
|
|
const char *response_hdr_hex;
|
|
apr_status_t status;
|
|
|
|
ha2 = build_digest_ha2(path, method, digest_info->qop, pool);
|
|
|
|
hdr = apr_psprintf(pool,
|
|
"Digest realm=\"%s\","
|
|
" username=\"%s\","
|
|
" nonce=\"%s\","
|
|
" uri=\"%s\"",
|
|
digest_info->realm, digest_info->username,
|
|
digest_info->nonce,
|
|
path);
|
|
|
|
if (digest_info->qop) {
|
|
if (! digest_info->cnonce)
|
|
digest_info->cnonce = random_cnonce(digest_info->pool);
|
|
|
|
hdr = apr_psprintf(pool, "%s, nc=%08x, cnonce=\"%s\", qop=\"%s\"",
|
|
hdr,
|
|
digest_info->digest_nc,
|
|
digest_info->cnonce,
|
|
digest_info->qop);
|
|
|
|
/* Build the response header:
|
|
MD5 hash of the combined HA1 result, server nonce (nonce),
|
|
request counter (nc), client nonce (cnonce),
|
|
quality of protection code (qop) and HA2 result. */
|
|
response = apr_psprintf(pool, "%s:%s:%08x:%s:%s:%s",
|
|
digest_info->ha1, digest_info->nonce,
|
|
digest_info->digest_nc,
|
|
digest_info->cnonce, digest_info->qop, ha2);
|
|
} else {
|
|
/* Build the response header:
|
|
MD5 hash of the combined HA1 result, server nonce (nonce)
|
|
and HA2 result. */
|
|
response = apr_psprintf(pool, "%s:%s:%s",
|
|
digest_info->ha1, digest_info->nonce, ha2);
|
|
}
|
|
|
|
status = apr_md5(response_hdr, response, strlen(response));
|
|
response_hdr_hex = hex_encode(response_hdr, pool);
|
|
|
|
hdr = apr_psprintf(pool, "%s, response=\"%s\"", hdr, response_hdr_hex);
|
|
|
|
if (digest_info->opaque) {
|
|
hdr = apr_psprintf(pool, "%s, opaque=\"%s\"", hdr,
|
|
digest_info->opaque);
|
|
}
|
|
if (digest_info->algorithm) {
|
|
hdr = apr_psprintf(pool, "%s, algorithm=\"%s\"", hdr,
|
|
digest_info->algorithm);
|
|
}
|
|
|
|
return hdr;
|
|
}
|
|
|
|
apr_status_t
|
|
serf__handle_digest_auth(int code,
|
|
serf_request_t *request,
|
|
serf_bucket_t *response,
|
|
const char *auth_hdr,
|
|
const char *auth_attr,
|
|
void *baton,
|
|
apr_pool_t *pool)
|
|
{
|
|
char *attrs;
|
|
char *nextkv;
|
|
const char *realm_name = NULL;
|
|
const char *nonce = NULL;
|
|
const char *algorithm = NULL;
|
|
const char *qop = NULL;
|
|
const char *opaque = NULL;
|
|
const char *key;
|
|
serf_connection_t *conn = request->conn;
|
|
serf_context_t *ctx = conn->ctx;
|
|
serf__authn_info_t *authn_info = (code == 401) ? &ctx->authn_info :
|
|
&ctx->proxy_authn_info;
|
|
digest_authn_info_t *digest_info = (code == 401) ? conn->authn_baton :
|
|
conn->proxy_authn_baton;
|
|
apr_status_t status;
|
|
apr_pool_t *cred_pool;
|
|
char *username, *password;
|
|
|
|
/* Can't do Digest authentication if there's no callback to get
|
|
username & password. */
|
|
if (!ctx->cred_cb) {
|
|
return SERF_ERROR_AUTHN_FAILED;
|
|
}
|
|
|
|
/* Need a copy cuz we're going to write NUL characters into the string. */
|
|
attrs = apr_pstrdup(pool, auth_attr);
|
|
|
|
/* We're expecting a list of key=value pairs, separated by a comma.
|
|
Ex. realm="SVN Digest",
|
|
nonce="f+zTl/leBAA=e371bd3070adfb47b21f5fc64ad8cc21adc371a5",
|
|
algorithm=MD5, qop="auth" */
|
|
for ( ; (key = apr_strtok(attrs, ",", &nextkv)) != NULL; attrs = NULL) {
|
|
char *val;
|
|
|
|
val = strchr(key, '=');
|
|
if (val == NULL)
|
|
continue;
|
|
*val++ = '\0';
|
|
|
|
/* skip leading spaces */
|
|
while (*key && *key == ' ')
|
|
key++;
|
|
|
|
/* If the value is quoted, then remove the quotes. */
|
|
if (*val == '"') {
|
|
apr_size_t last = strlen(val) - 1;
|
|
|
|
if (val[last] == '"') {
|
|
val[last] = '\0';
|
|
val++;
|
|
}
|
|
}
|
|
|
|
if (strcmp(key, "realm") == 0)
|
|
realm_name = val;
|
|
else if (strcmp(key, "nonce") == 0)
|
|
nonce = val;
|
|
else if (strcmp(key, "algorithm") == 0)
|
|
algorithm = val;
|
|
else if (strcmp(key, "qop") == 0)
|
|
qop = val;
|
|
else if (strcmp(key, "opaque") == 0)
|
|
opaque = val;
|
|
|
|
/* Ignore all unsupported attributes. */
|
|
}
|
|
|
|
if (!realm_name) {
|
|
return SERF_ERROR_AUTHN_MISSING_ATTRIBUTE;
|
|
}
|
|
|
|
authn_info->realm = apr_psprintf(conn->pool, "<%s://%s:%d> %s",
|
|
conn->host_info.scheme,
|
|
conn->host_info.hostname,
|
|
conn->host_info.port,
|
|
realm_name);
|
|
|
|
/* Ask the application for credentials */
|
|
apr_pool_create(&cred_pool, pool);
|
|
status = (*ctx->cred_cb)(&username, &password, request, baton,
|
|
code, authn_info->scheme->name,
|
|
authn_info->realm, cred_pool);
|
|
if (status) {
|
|
apr_pool_destroy(cred_pool);
|
|
return status;
|
|
}
|
|
|
|
digest_info->header = (code == 401) ? "Authorization" :
|
|
"Proxy-Authorization";
|
|
|
|
/* Store the digest authentication parameters in the context relative
|
|
to this connection, so we can use it to create the Authorization header
|
|
when setting up requests. */
|
|
digest_info->pool = conn->pool;
|
|
digest_info->qop = apr_pstrdup(digest_info->pool, qop);
|
|
digest_info->nonce = apr_pstrdup(digest_info->pool, nonce);
|
|
digest_info->cnonce = NULL;
|
|
digest_info->opaque = apr_pstrdup(digest_info->pool, opaque);
|
|
digest_info->algorithm = apr_pstrdup(digest_info->pool, algorithm);
|
|
digest_info->realm = apr_pstrdup(digest_info->pool, realm_name);
|
|
digest_info->username = apr_pstrdup(digest_info->pool, username);
|
|
digest_info->digest_nc++;
|
|
|
|
digest_info->ha1 = build_digest_ha1(username, password, digest_info->realm,
|
|
digest_info->pool);
|
|
|
|
apr_pool_destroy(cred_pool);
|
|
|
|
/* If the handshake is finished tell serf it can send as much requests as it
|
|
likes. */
|
|
serf_connection_set_max_outstanding_requests(conn, 0);
|
|
|
|
return APR_SUCCESS;
|
|
}
|
|
|
|
apr_status_t
|
|
serf__init_digest(int code,
|
|
serf_context_t *ctx,
|
|
apr_pool_t *pool)
|
|
{
|
|
return APR_SUCCESS;
|
|
}
|
|
|
|
apr_status_t
|
|
serf__init_digest_connection(int code,
|
|
serf_connection_t *conn,
|
|
apr_pool_t *pool)
|
|
{
|
|
/* Digest authentication is done per connection, so keep all progress
|
|
information per connection. */
|
|
if (code == 401) {
|
|
conn->authn_baton = apr_pcalloc(pool, sizeof(digest_authn_info_t));
|
|
} else {
|
|
conn->proxy_authn_baton = apr_pcalloc(pool, sizeof(digest_authn_info_t));
|
|
}
|
|
|
|
/* Make serf send the initial requests one by one */
|
|
serf_connection_set_max_outstanding_requests(conn, 1);
|
|
|
|
return APR_SUCCESS;
|
|
}
|
|
|
|
apr_status_t
|
|
serf__setup_request_digest_auth(peer_t peer,
|
|
int code,
|
|
serf_connection_t *conn,
|
|
serf_request_t *request,
|
|
const char *method,
|
|
const char *uri,
|
|
serf_bucket_t *hdrs_bkt)
|
|
{
|
|
digest_authn_info_t *digest_info = (peer == HOST) ? conn->authn_baton :
|
|
conn->proxy_authn_baton;
|
|
apr_status_t status = APR_SUCCESS;
|
|
|
|
if (digest_info && digest_info->realm) {
|
|
const char *value;
|
|
apr_uri_t parsed_uri;
|
|
|
|
/* TODO: per request pool? */
|
|
|
|
/* Extract path from uri. */
|
|
status = apr_uri_parse(conn->pool, uri, &parsed_uri);
|
|
|
|
/* Build a new Authorization header. */
|
|
digest_info->header = (peer == HOST) ? "Authorization" :
|
|
"Proxy-Authorization";
|
|
value = build_auth_header(digest_info, parsed_uri.path, method,
|
|
conn->pool);
|
|
|
|
serf_bucket_headers_setn(hdrs_bkt, digest_info->header,
|
|
value);
|
|
digest_info->digest_nc++;
|
|
|
|
/* Store the uri of this request on the serf_request_t object, to make
|
|
it available when validating the Authentication-Info header of the
|
|
matching response. */
|
|
request->auth_baton = parsed_uri.path;
|
|
}
|
|
|
|
return status;
|
|
}
|
|
|
|
apr_status_t
|
|
serf__validate_response_digest_auth(peer_t peer,
|
|
int code,
|
|
serf_connection_t *conn,
|
|
serf_request_t *request,
|
|
serf_bucket_t *response,
|
|
apr_pool_t *pool)
|
|
{
|
|
const char *key;
|
|
char *auth_attr;
|
|
char *nextkv;
|
|
const char *rspauth = NULL;
|
|
const char *qop = NULL;
|
|
const char *nc_str = NULL;
|
|
serf_bucket_t *hdrs;
|
|
digest_authn_info_t *digest_info = (peer == HOST) ? conn->authn_baton :
|
|
conn->proxy_authn_baton;
|
|
|
|
hdrs = serf_bucket_response_get_headers(response);
|
|
|
|
/* Need a copy cuz we're going to write NUL characters into the string. */
|
|
if (peer == HOST)
|
|
auth_attr = apr_pstrdup(pool,
|
|
serf_bucket_headers_get(hdrs, "Authentication-Info"));
|
|
else
|
|
auth_attr = apr_pstrdup(pool,
|
|
serf_bucket_headers_get(hdrs, "Proxy-Authentication-Info"));
|
|
|
|
/* If there's no Authentication-Info header there's nothing to validate. */
|
|
if (! auth_attr)
|
|
return APR_SUCCESS;
|
|
|
|
/* We're expecting a list of key=value pairs, separated by a comma.
|
|
Ex. rspauth="8a4b8451084b082be6b105e2b7975087",
|
|
cnonce="346531653132652d303033392d3435", nc=00000007,
|
|
qop=auth */
|
|
for ( ; (key = apr_strtok(auth_attr, ",", &nextkv)) != NULL; auth_attr = NULL) {
|
|
char *val;
|
|
|
|
val = strchr(key, '=');
|
|
if (val == NULL)
|
|
continue;
|
|
*val++ = '\0';
|
|
|
|
/* skip leading spaces */
|
|
while (*key && *key == ' ')
|
|
key++;
|
|
|
|
/* If the value is quoted, then remove the quotes. */
|
|
if (*val == '"') {
|
|
apr_size_t last = strlen(val) - 1;
|
|
|
|
if (val[last] == '"') {
|
|
val[last] = '\0';
|
|
val++;
|
|
}
|
|
}
|
|
|
|
if (strcmp(key, "rspauth") == 0)
|
|
rspauth = val;
|
|
else if (strcmp(key, "qop") == 0)
|
|
qop = val;
|
|
else if (strcmp(key, "nc") == 0)
|
|
nc_str = val;
|
|
}
|
|
|
|
if (rspauth) {
|
|
const char *ha2, *tmp, *resp_hdr_hex;
|
|
unsigned char resp_hdr[APR_MD5_DIGESTSIZE];
|
|
const char *req_uri = request->auth_baton;
|
|
|
|
ha2 = build_digest_ha2(req_uri, "", qop, pool);
|
|
tmp = apr_psprintf(pool, "%s:%s:%s:%s:%s:%s",
|
|
digest_info->ha1, digest_info->nonce, nc_str,
|
|
digest_info->cnonce, digest_info->qop, ha2);
|
|
apr_md5(resp_hdr, tmp, strlen(tmp));
|
|
resp_hdr_hex = hex_encode(resp_hdr, pool);
|
|
|
|
/* Incorrect response-digest in Authentication-Info header. */
|
|
if (strcmp(rspauth, resp_hdr_hex) != 0) {
|
|
return SERF_ERROR_AUTHN_FAILED;
|
|
}
|
|
}
|
|
|
|
return APR_SUCCESS;
|
|
}
|