#include "httpd.h"
#include "http_config.h"
#include "http_conf_globals.h"
#include "http_core.h"
#include "http_request.h"
#include "http_log.h"
#include "http_protocol.h"
#include "ap_config.h"
#include "ap_ctype.h"
#include "util_uri.h"
#include "util_md5.h"
#include "ap_sha1.h"
#ifdef WIN32
#include <wincrypt.h>
#endif
#ifdef HAVE_SHMEM_MM
#include "mm.h"
#endif /* HAVE_SHMEM_MM */
typedef
struct
digest_config_struct {
const
char
*dir_name;
const
char
*pwfile;
const
char
*grpfile;
const
char
*realm;
const
char
**qop_list;
AP_SHA1_CTX nonce_ctx;
long
nonce_lifetime;
const
char
*nonce_format;
int
check_nc;
const
char
*algorithm;
char
*uri_list;
const
char
*ha1;
} digest_config_rec;
#define DFLT_ALGORITHM "MD5"
#define DFLT_NONCE_LIFE 300L
#define NEXTNONCE_DELTA 30
#define NONCE_TIME_LEN (((sizeof(time_t)+2)/3)*4)
#define NONCE_HASH_LEN (2*SHA_DIGESTSIZE)
#define NONCE_LEN (NONCE_TIME_LEN + NONCE_HASH_LEN)
#define SECRET_LEN 20
typedef
struct
hash_entry {
unsigned
long
key;
struct
hash_entry *next;
unsigned
long
nonce_count;
char
ha1[2*MD5_DIGESTSIZE+1];
char
last_nonce[NONCE_LEN+1];
} client_entry;
static
struct
hash_table {
client_entry **table;
unsigned
long
tbl_len;
unsigned
long
num_entries;
unsigned
long
num_created;
unsigned
long
num_removed;
unsigned
long
num_renewed;
} *client_list;
enum
hdr_sts { NO_HEADER, NOT_DIGEST, INVALID, VALID };
typedef
struct
digest_header_struct {
const
char
*scheme;
const
char
*realm;
const
char
*username;
char
*nonce;
const
char
*uri;
const
char
*digest;
const
char
*algorithm;
const
char
*cnonce;
const
char
*opaque;
unsigned
long
opaque_num;
const
char
*message_qop;
const
char
*nonce_count;
time_t
nonce_time;
enum
hdr_sts auth_hdr_sts;
const
char
*raw_request_uri;
uri_components *psd_request_uri;
int
needed_auth;
client_entry *client;
} digest_header_rec;
typedef
union
time_union {
time_t
time
;
unsigned
char
arr[
sizeof
(
time_t
)];
} time_rec;
static
unsigned
char
secret[SECRET_LEN];
static
int
call_cnt = 0;
#ifdef HAVE_SHMEM_MM
static
MM *opaque_mm;
static
unsigned
long
*opaque_cntr;
static
MM *client_mm;
static
MM *otn_count_mm;
static
time_t
*otn_counter;
#define SHMEM_SIZE 1000 /* ~ 12 entries */
#define NUM_BUCKETS 15UL
#else /* HAVE_SHMEM_MM */
static
void
*client_mm = NULL;
#endif /* HAVE_SHMEM_MM */
module MODULE_VAR_EXPORT digest_auth_module;
#ifdef HAVE_SHMEM_MM
static
void
cleanup_tables(
void
*not_used)
{
fprintf
(stderr,
"Digest: cleaning up shared memory\n"
);
fflush
(stderr);
if
(client_mm) {
mm_destroy(client_mm);
client_mm = NULL;
}
if
(opaque_mm) {
mm_destroy(opaque_mm);
opaque_mm = NULL;
}
if
(otn_count_mm) {
mm_destroy(otn_count_mm);
otn_count_mm = NULL;
}
}
#endif /* HAVE_SHMEM_MM */
#ifdef WIN32
static
void
initialize_secret(server_rec *s)
{
HCRYPTPROV hProv;
ap_log_error(APLOG_MARK, APLOG_NOERRNO|APLOG_NOTICE, s,
"Digest: generating secret for digest authentication ..."
);
if
(!CryptAcquireContext(&hProv,NULL,NULL,PROV_RSA_FULL,0)) {
ap_log_error(APLOG_MARK, APLOG_CRIT, s,
"Digest: Error acquiring context. Errno = %d"
,
GetLastError());
exit
(EXIT_FAILURE);
}
if
(!CryptGenRandom(hProv,
sizeof
(secret),secret)) {
ap_log_error(APLOG_MARK, APLOG_CRIT, s,
"Digest: Error generating secret. Errno = %d"
,
GetLastError());
exit
(EXIT_FAILURE);
}
ap_log_error(APLOG_MARK, APLOG_NOERRNO|APLOG_NOTICE, s,
"Digest: done"
);
}
#else
static
void
initialize_secret(server_rec *s)
{
#ifdef DEV_RANDOM
int
rnd;
ssize_t got;
size_t
tot;
#else
extern
int
randbyte(
void
);
unsigned
int
idx;
#endif
ap_log_error(APLOG_MARK, APLOG_NOERRNO|APLOG_NOTICE, s,
"Digest: generating secret for digest authentication ..."
);
#ifdef DEV_RANDOM
#define XSTR(x) #x
#define STR(x) XSTR(x)
if
((rnd = open(STR(DEV_RANDOM), O_RDONLY)) == -1) {
ap_log_error(APLOG_MARK, APLOG_CRIT, s,
"Digest: Couldn't open "
STR(DEV_RANDOM));
exit
(EXIT_FAILURE);
}
for
(tot=0; tot<
sizeof
(secret); tot += got) {
if
((got = read(rnd, secret+tot,
sizeof
(secret)-tot)) < 0) {
ap_log_error(APLOG_MARK, APLOG_CRIT, s,
"Digest: Error reading "
STR(DEV_RANDOM));
exit
(EXIT_FAILURE);
}
}
close(rnd);
#undef STR
#undef XSTR
#else /* use truerand */
for
(idx=0; idx<
sizeof
(secret); idx++)
secret[idx] = (unsigned
char
) randbyte();
#endif /* DEV_RANDOM */
ap_log_error(APLOG_MARK, APLOG_NOERRNO|APLOG_NOTICE, s,
"Digest: done"
);
}
#endif
#ifdef HAVE_SHMEM_MM
static
void
initialize_tables(server_rec *s)
{
unsigned
long
idx;
client_mm = mm_create(SHMEM_SIZE,
tmpnam
(NULL));
if
(client_mm == NULL)
goto
failed;
#ifdef MPE
if
(geteuid() == 1) {
#else
if
(geteuid() == 0) {
#endif
if
(mm_permission(client_mm, 0600, ap_user_id, ap_group_id))
goto
failed;
}
client_list = mm_malloc(client_mm,
sizeof
(*client_list) +
sizeof
(client_entry*)*NUM_BUCKETS);
if
(!client_list)
goto
failed;
client_list->table = (client_entry**) (client_list + 1);
for
(idx=0; idx<NUM_BUCKETS; idx++)
client_list->table[idx] = NULL;
client_list->tbl_len = NUM_BUCKETS;
client_list->num_entries = 0;
opaque_mm = mm_create(
sizeof
(*opaque_cntr),
tmpnam
(NULL));
if
(opaque_mm == NULL)
goto
failed;
#ifdef MPE
if
(geteuid() == 1) {
#else
if
(geteuid() == 0) {
#endif
if
(mm_permission(opaque_mm, 0600, ap_user_id, ap_group_id))
goto
failed;
}
opaque_cntr = mm_malloc(opaque_mm,
sizeof
(*opaque_cntr));
if
(opaque_cntr == NULL)
goto
failed;
*opaque_cntr = 1UL;
otn_count_mm = mm_create(
sizeof
(*otn_counter),
tmpnam
(NULL));
if
(otn_count_mm == NULL)
goto
failed;
#ifdef MPE
if
(geteuid() == 1) {
#else
if
(geteuid() == 0) {
#endif
if
(mm_permission(otn_count_mm, 0600, ap_user_id, ap_group_id))
goto
failed;
}
otn_counter = mm_malloc(otn_count_mm,
sizeof
(*otn_counter));
if
(otn_counter == NULL)
goto
failed;
*otn_counter = 0;
return
;
failed:
if
(!client_mm || (client_list && client_list->table && !opaque_mm)
|| (opaque_cntr && !otn_count_mm))
ap_log_error(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, s,
"Digest: failed to create shared memory segments; reason "
"was `%s' - all nonce-count checking, one-time nonces, "
"and MD5-sess algorithm disabled"
, mm_error());
else
ap_log_error(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, s,
"Digest: failed to allocate shared mem; reason was `%s' "
"- all nonce-count checking, one-time nonces, and "
"MD5-sess algorithm disabled"
, mm_error());
cleanup_tables(NULL);
}
#endif /* HAVE_SHMEM_MM */
static
void
initialize_module(server_rec *s, pool *p)
{
if
(++call_cnt < 2)
return
;
if
(call_cnt == 2)
initialize_secret(s);
#ifdef HAVE_SHMEM_MM
initialize_tables(s);
ap_register_cleanup(p, NULL, cleanup_tables, ap_null_cleanup);
#endif /* HAVE_SHMEM_MM */
}
static
void
*create_digest_dir_config(pool *p,
char
*dir)
{
digest_config_rec *conf;
if
(dir == NULL)
return
NULL;
conf = (digest_config_rec *) ap_pcalloc(p,
sizeof
(digest_config_rec));
if
(conf) {
conf->qop_list = ap_palloc(p,
sizeof
(
char
*));
conf->qop_list[0] = NULL;
conf->nonce_lifetime = DFLT_NONCE_LIFE;
conf->dir_name = ap_pstrdup(p, dir);
conf->algorithm = DFLT_ALGORITHM;
}
return
conf;
}
static
const
char
*set_realm(cmd_parms *cmd,
void
*config,
const
char
*realm)
{
digest_config_rec *conf = (digest_config_rec *) config;
conf->realm = realm;
ap_SHA1Init(&conf->nonce_ctx);
ap_SHA1Update_binary(&conf->nonce_ctx, secret,
sizeof
(secret));
ap_SHA1Update_binary(&conf->nonce_ctx, (
const
unsigned
char
*) realm,
strlen
(realm));
return
DECLINE_CMD;
}
static
const
char
*set_digest_file(cmd_parms *cmd,
void
*config,
const
char
*file)
{
((digest_config_rec *) config)->pwfile = file;
return
NULL;
}
static
const
char
*set_group_file(cmd_parms *cmd,
void
*config,
const
char
*file)
{
((digest_config_rec *) config)->grpfile = file;
return
NULL;
}
static
const
char
*set_qop(cmd_parms *cmd,
void
*config,
const
char
*op)
{
digest_config_rec *conf = (digest_config_rec *) config;
char
**tmp;
int
cnt;
if
(!strcasecmp(op,
"none"
)) {
if
(conf->qop_list[0] == NULL) {
conf->qop_list = ap_palloc(cmd->pool, 2 *
sizeof
(
char
*));
conf->qop_list[1] = NULL;
}
conf->qop_list[0] =
"none"
;
return
NULL;
}
if
(!strcasecmp(op,
"auth-int"
))
ap_log_error(APLOG_MARK, APLOG_NOERRNO|APLOG_WARNING, cmd->server,
"Digest: WARNING: qop `auth-int' currently only works "
"correctly for responses with no entity"
);
else
if
(strcasecmp(op,
"auth"
))
return
ap_pstrcat(cmd->pool,
"Unrecognized qop: "
, op, NULL);
for
(cnt=0; conf->qop_list[cnt] != NULL; cnt++)
;
tmp = ap_palloc(cmd->pool, (cnt+2)*
sizeof
(
char
*));
memcpy
(tmp, conf->qop_list, cnt*
sizeof
(
char
*));
tmp[cnt] = ap_pstrdup(cmd->pool, op);
tmp[cnt+1] = NULL;
conf->qop_list = (
const
char
**)tmp;
return
NULL;
}
static
const
char
*set_nonce_lifetime(cmd_parms *cmd,
void
*config,
const
char
*t)
{
char
*endptr;
long
lifetime;
lifetime =
strtol
(t, &endptr, 10);
if
(endptr < (t+
strlen
(t)) && !ap_isspace(*endptr))
return
ap_pstrcat(cmd->pool,
"Invalid time in AuthDigestNonceLifetime: "
, t, NULL);
((digest_config_rec *) config)->nonce_lifetime = lifetime;
return
NULL;
}
static
const
char
*set_nonce_format(cmd_parms *cmd,
void
*config,
const
char
*fmt)
{
((digest_config_rec *) config)->nonce_format = fmt;
return
"AuthDigestNonceFormat is not implemented (yet)"
;
}
static
const
char
*set_nc_check(cmd_parms *cmd,
void
*config,
int
flag)
{
((digest_config_rec *) config)->check_nc = flag;
return
NULL;
}
static
const
char
*set_algorithm(cmd_parms *cmd,
void
*config,
const
char
*alg)
{
if
(!strcasecmp(alg,
"MD5-sess"
))
#ifdef HAVE_SHMEM_MM
;
#else /* HAVE_SHMEM_MM */
ap_log_error(APLOG_MARK, APLOG_NOERRNO|APLOG_WARNING, cmd->server,
"Digest: WARNING: algorithm `MD5-sess' is currently not "
"correctly implemented"
);
#endif /* HAVE_SHMEM_MM */
else
if
(strcasecmp(alg,
"MD5"
))
return
ap_pstrcat(cmd->pool,
"Invalid algorithm in AuthDigestAlgorithm: "
, alg, NULL);
((digest_config_rec *) config)->algorithm = alg;
return
NULL;
}
static
const
char
*set_uri_list(cmd_parms *cmd,
void
*config,
const
char
*uri)
{
digest_config_rec *c = (digest_config_rec *) config;
if
(c->uri_list) {
c->uri_list[
strlen
(c->uri_list)-1] =
'\0'
;
c->uri_list = ap_pstrcat(cmd->pool, c->uri_list,
" "
, uri,
"\""
, NULL);
}
else
c->uri_list = ap_pstrcat(cmd->pool,
", domain=\""
, uri,
"\""
, NULL);
return
NULL;
}
static
const
command_rec digest_cmds[] =
{
{
"AuthName"
, set_realm, NULL, OR_AUTHCFG, TAKE1,
"The authentication realm (e.g. \"Members Only\")"
},
{
"AuthDigestFile"
, set_digest_file, NULL, OR_AUTHCFG, TAKE1,
"The name of the file containing the usernames and password hashes"
},
{
"AuthDigestGroupFile"
, set_group_file, NULL, OR_AUTHCFG, TAKE1,
"The name of the file containing the group names and members"
},
{
"AuthDigestQop"
, set_qop, NULL, OR_AUTHCFG, ITERATE,
"A list of quality-of-protection options"
},
{
"AuthDigestNonceLifetime"
, set_nonce_lifetime, NULL, OR_AUTHCFG, TAKE1,
"Maximum lifetime of the server nonce (seconds)"
},
{
"AuthDigestNonceFormat"
, set_nonce_format, NULL, OR_AUTHCFG, TAKE1,
"The format to use when generating the server nonce"
},
{
"AuthDigestNcCheck"
, set_nc_check, NULL, OR_AUTHCFG, FLAG,
"Whether or not to check the nonce-count sent by the client"
},
{
"AuthDigestAlgorithm"
, set_algorithm, NULL, OR_AUTHCFG, TAKE1,
"The algorithm used for the hash calculation"
},
{
"AuthDigestDomain"
, set_uri_list, NULL, OR_AUTHCFG, ITERATE,
"A list of URI's which belong to the same protection space as the current URI"
},
{NULL, NULL, NULL, 0, 0, NULL}
};
#ifdef HAVE_SHMEM_MM
static
client_entry *get_client(unsigned
long
key,
const
request_rec *r)
{
int
bucket;
client_entry *entry, *prev = NULL;
if
(!key || !client_mm)
return
NULL;
bucket = key % client_list->tbl_len;
entry = client_list->table[bucket];
mm_lock(client_mm, MM_LOCK_RD);
while
(entry && key != entry->key) {
prev = entry;
entry = entry->next;
}
if
(entry && prev) {
prev->next = entry->next;
entry->next = client_list->table[bucket];
client_list->table[bucket] = entry;
}
mm_unlock(client_mm);
if
(entry)
ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_DEBUG, r,
"get_client(): client %lu found"
, key);
else
ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_DEBUG, r,
"get_client(): client %lu not found"
, key);
return
entry;
}
static
long
gc(
void
)
{
client_entry *entry, *prev;
unsigned
long
num_removed = 0, idx;
for
(idx=0; idx<client_list->tbl_len; idx++) {
entry = client_list->table[idx];
prev = NULL;
while
(entry->next) {
prev = entry;
entry = entry->next;
}
if
(prev) prev->next = NULL;
else
client_list->table[idx] = NULL;
if
(entry) {
mm_free(client_mm, entry);
num_removed++;
}
}
client_list->num_entries -= num_removed;
client_list->num_removed += num_removed;
return
num_removed;
}
static
client_entry *add_client(unsigned
long
key, client_entry *info,
server_rec *s)
{
int
bucket;
client_entry *entry;
if
(!key || !client_mm)
return
NULL;
bucket = key % client_list->tbl_len;
entry = client_list->table[bucket];
mm_lock(client_mm, MM_LOCK_RW);
entry = mm_malloc(client_mm,
sizeof
(client_entry));
if
(!entry) {
long
num_removed = gc();
ap_log_error(APLOG_MARK, APLOG_NOERRNO|APLOG_INFO, s,
"Digest: gc'd %ld client entries. Total new clients: "
"%ld; Total removed clients: %ld; Total renewed clients: "
"%ld"
, num_removed,
client_list->num_created - client_list->num_renewed,
client_list->num_removed, client_list->num_renewed);
entry = mm_malloc(client_mm,
sizeof
(client_entry));
if
(!entry)
return
NULL;
}
memcpy
(entry, info,
sizeof
(client_entry));
entry->key = key;
entry->next = client_list->table[bucket];
client_list->table[bucket] = entry;
client_list->num_created++;
client_list->num_entries++;
mm_unlock(client_mm);
ap_log_error(APLOG_MARK, APLOG_NOERRNO|APLOG_DEBUG, s,
"allocated new client %lu"
, key);
return
entry;
}
#else /* HAVE_SHMEM_MM */
static
client_entry *get_client(unsigned
long
key,
const
request_rec *r)
{
return
NULL;
}
#endif /* HAVE_SHMEM_MM */
static
int
get_digest_rec(request_rec *r, digest_header_rec *resp)
{
const
char
*auth_line;
size_t
l;
int
vk = 0, vv = 0;
char
*key, *value;
auth_line = ap_table_get(r->headers_in,
r->proxyreq == STD_PROXY ?
"Proxy-Authorization"
:
"Authorization"
);
if
(!auth_line) {
resp->auth_hdr_sts = NO_HEADER;
return
!OK;
}
resp->scheme = ap_getword_white(r->pool, &auth_line);
if
(strcasecmp(resp->scheme,
"Digest"
)) {
resp->auth_hdr_sts = NOT_DIGEST;
return
!OK;
}
l =
strlen
(auth_line);
key = ap_palloc(r->pool, l+1);
value = ap_palloc(r->pool, l+1);
while
(auth_line[0] !=
'\0'
) {
while
(ap_isspace(auth_line[0])) auth_line++;
vk = 0;
while
(auth_line[0] !=
'='
&& auth_line[0] !=
','
&& auth_line[0] !=
'\0'
&& !ap_isspace(auth_line[0]))
key[vk++] = *auth_line++;
key[vk] =
'\0'
;
while
(ap_isspace(auth_line[0])) auth_line++;
if
(auth_line[0] ==
'='
) {
auth_line++;
while
(ap_isspace(auth_line[0])) auth_line++;
vv = 0;
if
(auth_line[0] ==
'\"'
) {
auth_line++;
while
(auth_line[0] !=
'\"'
&& auth_line[0] !=
'\0'
) {
if
(auth_line[0] ==
'\\'
&& auth_line[1] !=
'\0'
)
auth_line++;
value[vv++] = *auth_line++;
}
if
(auth_line[0] !=
'\0'
) auth_line++;
}
else
{
while
(auth_line[0] !=
','
&& auth_line[0] !=
'\0'
&& !ap_isspace(auth_line[0]))
value[vv++] = *auth_line++;
}
value[vv] =
'\0'
;
}
while
(auth_line[0] !=
','
&& auth_line[0] !=
'\0'
) auth_line++;
if
(auth_line[0] !=
'\0'
) auth_line++;
if
(!strcasecmp(key,
"username"
))
resp->username = ap_pstrdup(r->pool, value);
else
if
(!strcasecmp(key,
"realm"
))
resp->realm = ap_pstrdup(r->pool, value);
else
if
(!strcasecmp(key,
"nonce"
))
resp->nonce = ap_pstrdup(r->pool, value);
else
if
(!strcasecmp(key,
"uri"
))
resp->uri = ap_pstrdup(r->pool, value);
else
if
(!strcasecmp(key,
"response"
))
resp->digest = ap_pstrdup(r->pool, value);
else
if
(!strcasecmp(key,
"algorithm"
))
resp->algorithm = ap_pstrdup(r->pool, value);
else
if
(!strcasecmp(key,
"cnonce"
))
resp->cnonce = ap_pstrdup(r->pool, value);
else
if
(!strcasecmp(key,
"opaque"
))
resp->opaque = ap_pstrdup(r->pool, value);
else
if
(!strcasecmp(key,
"qop"
))
resp->message_qop = ap_pstrdup(r->pool, value);
else
if
(!strcasecmp(key,
"nc"
))
resp->nonce_count = ap_pstrdup(r->pool, value);
}
if
(!resp->username || !resp->realm || !resp->nonce || !resp->uri
|| !resp->digest
|| (resp->message_qop && (!resp->cnonce || !resp->nonce_count))) {
resp->auth_hdr_sts = INVALID;
return
!OK;
}
if
(resp->opaque)
resp->opaque_num = (unsigned
long
)
strtol
(resp->opaque, NULL, 16);
resp->auth_hdr_sts = VALID;
return
OK;
}
static
int
update_nonce_count(request_rec *r)
{
digest_header_rec *resp;
int
res;
if
(!ap_is_initial_req(r))
return
DECLINED;
resp = ap_pcalloc(r->pool,
sizeof
(digest_header_rec));
resp->raw_request_uri = r->unparsed_uri;
resp->psd_request_uri = &r->parsed_uri;
resp->needed_auth = 0;
ap_set_module_config(r->request_config, &digest_auth_module, resp);
res = get_digest_rec(r, resp);
resp->client = get_client(resp->opaque_num, r);
if
(res == OK && resp->client)
resp->client->nonce_count++;
return
DECLINED;
}
static
void
gen_nonce_hash(
char
*hash,
const
char
*timestr,
const
char
*opaque,
const
server_rec *server,
const
digest_config_rec *conf)
{
const
char
*hex =
"0123456789abcdef"
;
unsigned
char
sha1[SHA_DIGESTSIZE];
AP_SHA1_CTX ctx;
int
idx;
memcpy
(&ctx, &conf->nonce_ctx,
sizeof
(ctx));
ap_SHA1Update_binary(&ctx, (
const
unsigned
char
*) timestr,
strlen
(timestr));
if
(opaque)
ap_SHA1Update_binary(&ctx, (
const
unsigned
char
*) opaque,
strlen
(opaque));
ap_SHA1Final(sha1, &ctx);
for
(idx=0; idx<SHA_DIGESTSIZE; idx++) {
*hash++ = hex[sha1[idx] >> 4];
*hash++ = hex[sha1[idx] & 0xF];
}
*hash++ =
'\0'
;
}
static
const
char
*gen_nonce(pool *p,
time_t
now,
const
char
*opaque,
const
server_rec *server,
const
digest_config_rec *conf)
{
char
*nonce = ap_palloc(p, NONCE_LEN+1);
time_rec t;
if
(conf->nonce_lifetime != 0)
t.
time
= now;
else
#ifdef HAVE_SHMEM_MM
t.
time
= (*otn_counter)++;
#else /* HAVE_SHMEM_MM */
t.
time
= 42;
#endif /* HAVE_SHMEM_MM */
ap_base64encode_binary(nonce, t.arr,
sizeof
(t.arr));
gen_nonce_hash(nonce+NONCE_TIME_LEN, nonce, opaque, server, conf);
return
nonce;
}
#ifdef HAVE_SHMEM_MM
static
client_entry *gen_client(
const
request_rec *r)
{
unsigned
long
op;
client_entry new_entry = { 0, NULL, 0,
""
,
""
}, *entry;
if
(!opaque_mm)
return
0;
mm_lock(opaque_mm, MM_LOCK_RW);
op = (*opaque_cntr)++;
mm_unlock(opaque_mm);
if
(!(entry = add_client(op, &new_entry, r->server))) {
ap_log_rerror(APLOG_MARK, APLOG_ERR, r,
"Digest: failed to allocate client entry - ignoring "
"client"
);
return
NULL;
}
return
entry;
}
#else /* HAVE_SHMEM_MM */
static
client_entry *gen_client(
const
request_rec *r) {
return
NULL; }
#endif /* HAVE_SHMEM_MM */
static
const
char
*get_userpw_hash(
const
request_rec *r,
const
digest_header_rec *resp,
const
digest_config_rec *conf)
{
return
ap_md5(r->pool,
(unsigned
char
*) ap_pstrcat(r->pool, conf->ha1,
":"
, resp->nonce,
":"
, resp->cnonce, NULL));
}
static
const
char
*get_session_HA1(
const
request_rec *r,
digest_header_rec *resp,
const
digest_config_rec *conf,
int
generate)
{
const
char
*ha1 = NULL;
if
(resp->opaque && resp->client && resp->client->ha1[0])
return
resp->client->ha1;
else
if
(!generate)
return
NULL;
if
(!resp->client)
resp->client = gen_client(r);
if
(resp->client) {
ha1 = get_userpw_hash(r, resp, conf);
if
(ha1)
memcpy
(resp->client->ha1, ha1,
sizeof
(resp->client->ha1));
}
return
ha1;
}
static
void
clear_session(
const
digest_header_rec *resp)
{
if
(resp->client)
resp->client->ha1[0] =
'\0'
;
}
static
const
char
*guess_domain(pool *p,
const
char
*uri,
const
char
*filename,
const
char
*dir)
{
size_t
u_len =
strlen
(uri), f_len =
strlen
(filename), d_len =
strlen
(dir);
const
char
*u, *f;
if
(u_len >= d_len && !
memcmp
(uri, dir, d_len))
return
dir;
if
(dir[0] !=
'/'
)
return
dir;
u = uri + u_len - 1;
while
(u > uri && *u !=
'/'
) u--;
while
(*u && *u !=
'.'
) u++;
if
(*u ==
'.'
) u--;
if
(*u ==
'/'
) u--;
f = filename + f_len - 1;
while
(f > filename && *f !=
'/'
) f--;
while
(*f && *f !=
'.'
) f++;
if
(*f ==
'.'
) f--;
if
(*f ==
'/'
) f--;
while
(*f == *u && f > filename && u > uri) u--, f--;
f++; u++;
while
(*f && *f !=
'/'
) f++, u++;
if
((unsigned
long
) (f-filename) < d_len) {
char
*tmp = ap_pstrdup(p, uri);
tmp[(u-uri)+(d_len-(f-filename))] =
'\0'
;
return
tmp;
}
return
""
;
}
static
const
char
*ltox(pool *p, unsigned
long
num)
{
if
(num != 0)
return
ap_psprintf(p,
"%lx"
, num);
else
return
""
;
}
static
void
note_digest_auth_failure(request_rec *r,
const
digest_config_rec *conf,
digest_header_rec *resp,
int
stale)
{
const
char
*qop, *opaque, *opaque_param, *domain, *nonce;
int
cnt;
if
(conf->qop_list[0] == NULL)
qop =
", qop=\"auth\""
;
else
if
(!strcasecmp(conf->qop_list[0],
"none"
))
qop =
""
;
else
{
qop = ap_pstrcat(r->pool,
", qop=\""
, conf->qop_list[0], NULL);
for
(cnt=1; conf->qop_list[cnt] != NULL; cnt++)
qop = ap_pstrcat(r->pool, qop,
","
, conf->qop_list[cnt], NULL);
qop = ap_pstrcat(r->pool, qop,
"\""
, NULL);
}
if
(resp->opaque == NULL) {
if
((conf->check_nc || conf->nonce_lifetime == 0
|| !strcasecmp(conf->algorithm,
"MD5-sess"
))
&& (resp->client = gen_client(r)) != NULL)
opaque = ltox(r->pool, resp->client->key);
else
opaque =
""
;
}
else
if
(resp->client == NULL) {
resp->client = gen_client(r);
if
(resp->client != NULL) {
opaque = ltox(r->pool, resp->client->key);
stale = 1;
client_list->num_renewed++;
}
else
opaque =
""
;
}
else
{
opaque = resp->opaque;
resp->client->nonce_count = 0;
}
if
(opaque[0])
opaque_param = ap_pstrcat(r->pool,
", opaque=\""
, opaque,
"\""
, NULL);
else
opaque_param = NULL;
nonce = gen_nonce(r->pool, r->request_time, opaque, r->server, conf);
if
(resp->client && conf->nonce_lifetime == 0)
memcpy
(resp->client->last_nonce, nonce, NONCE_LEN+1);
if
(!strcasecmp(conf->algorithm,
"MD5-sess"
))
clear_session(resp);
if
(r->proxyreq != NOT_PROXY)
domain = NULL;
else
if
(conf->uri_list)
domain = conf->uri_list;
else
{
domain = guess_domain(r->pool, resp->psd_request_uri->path, r->filename,
conf->dir_name);
if
(domain[0] ==
'/'
&& domain[1] ==
'\0'
)
domain = NULL;
else
domain = ap_pstrcat(r->pool,
", domain=\""
, domain,
"\""
, NULL);
}
ap_table_mergen(r->err_headers_out,
r->proxyreq == STD_PROXY ?
"Proxy-Authenticate"
:
"WWW-Authenticate"
,
ap_psprintf(r->pool,
"Digest realm=\"%s\", nonce=\"%s\", "
"algorithm=%s%s%s%s%s"
,
ap_auth_name(r), nonce, conf->algorithm,
opaque_param ? opaque_param :
""
,
domain ? domain :
""
,
stale ?
", stale=true"
:
""
, qop));
}
static
const
char
*get_hash(request_rec *r,
const
char
*user,
const
char
*realm,
const
char
*auth_pwfile)
{
configfile_t *f;
char
l[MAX_STRING_LEN];
const
char
*rpw;
char
*w, *x;
if
(!(f = ap_pcfg_openfile(r->pool, auth_pwfile))) {
ap_log_rerror(APLOG_MARK, APLOG_ERR, r,
"Digest: Could not open password file: %s"
, auth_pwfile);
return
NULL;
}
while
(!(ap_cfg_getline(l, MAX_STRING_LEN, f))) {
if
((l[0] ==
'#'
) || (!l[0]))
continue
;
rpw = l;
w = ap_getword(r->pool, &rpw,
':'
);
x = ap_getword(r->pool, &rpw,
':'
);
if
(x && w && !
strcmp
(user, w) && !
strcmp
(realm, x)) {
ap_cfg_closefile(f);
return
ap_pstrdup(r->pool, rpw);
}
}
ap_cfg_closefile(f);
return
NULL;
}
static
int
check_nc(
const
request_rec *r,
const
digest_header_rec *resp,
const
digest_config_rec *conf)
{
unsigned
long
nc;
const
char
*snc = resp->nonce_count;
char
*endptr;
if
(!conf->check_nc || !client_mm)
return
OK;
nc =
strtol
(snc, &endptr, 16);
if
(endptr < (snc+
strlen
(snc)) && !ap_isspace(*endptr)) {
ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, r,
"Digest: invalid nc %s received - not a number"
, snc);
return
!OK;
}
if
(!resp->client)
return
!OK;
if
(nc != resp->client->nonce_count) {
ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, r,
"Digest: Warning, possible replay attack: nonce-count "
"check failed: %lu != %lu"
, nc,
resp->client->nonce_count);
return
!OK;
}
return
OK;
}
static
int
check_nonce(request_rec *r, digest_header_rec *resp,
const
digest_config_rec *conf)
{
double
dt;
time_rec nonce_time;
char
tmp, hash[NONCE_HASH_LEN+1];
if
(
strlen
(resp->nonce) != NONCE_LEN) {
ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, r,
"Digest: invalid nonce %s received - length is not %d"
,
resp->nonce, NONCE_LEN);
note_digest_auth_failure(r, conf, resp, 1);
return
AUTH_REQUIRED;
}
tmp = resp->nonce[NONCE_TIME_LEN];
resp->nonce[NONCE_TIME_LEN] =
'\0'
;
ap_base64decode_binary(nonce_time.arr, resp->nonce);
gen_nonce_hash(hash, resp->nonce, resp->opaque, r->server, conf);
resp->nonce[NONCE_TIME_LEN] = tmp;
resp->nonce_time = nonce_time.
time
;
if
(
strcmp
(hash, resp->nonce+NONCE_TIME_LEN)) {
ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, r,
"Digest: invalid nonce %s received - hash is not %s"
,
resp->nonce, hash);
note_digest_auth_failure(r, conf, resp, 1);
return
AUTH_REQUIRED;
}
dt =
difftime
(r->request_time, nonce_time.
time
);
if
(conf->nonce_lifetime > 0 && dt < 0) {
ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, r,
"Digest: invalid nonce %s received - user attempted "
"time travel"
, resp->nonce);
note_digest_auth_failure(r, conf, resp, 1);
return
AUTH_REQUIRED;
}
if
(conf->nonce_lifetime > 0) {
if
(dt > conf->nonce_lifetime) {
ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_INFO, r,
"Digest: user %s: nonce expired - sending new nonce"
,
r->connection->user);
note_digest_auth_failure(r, conf, resp, 1);
return
AUTH_REQUIRED;
}
}
else
if
(conf->nonce_lifetime == 0 && resp->client) {
if
(
memcmp
(resp->client->last_nonce, resp->nonce, NONCE_LEN)) {
ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_INFO, r,
"Digest: user %s: one-time-nonce mismatch - sending "
"new nonce"
, r->connection->user);
note_digest_auth_failure(r, conf, resp, 1);
return
AUTH_REQUIRED;
}
}
return
OK;
}
static
const
char
*old_digest(
const
request_rec *r,
const
digest_header_rec *resp,
const
char
*ha1)
{
const
char
*ha2;
ha2 = ap_md5(r->pool, (unsigned
char
*)ap_pstrcat(r->pool, r->method,
":"
,
resp->uri, NULL));
return
ap_md5(r->pool,
(unsigned
char
*)ap_pstrcat(r->pool, ha1,
":"
, resp->nonce,
":"
, ha2, NULL));
}
static
const
char
*new_digest(
const
request_rec *r,
digest_header_rec *resp,
const
digest_config_rec *conf)
{
const
char
*ha1, *ha2, *a2;
if
(resp->algorithm && !strcasecmp(resp->algorithm,
"MD5-sess"
)) {
ha1 = get_session_HA1(r, resp, conf, 1);
if
(!ha1)
return
NULL;
}
else
ha1 = conf->ha1;
if
(resp->message_qop && !strcasecmp(resp->message_qop,
"auth-int"
))
a2 = ap_pstrcat(r->pool, r->method,
":"
, resp->uri,
":"
,
ap_md5(r->pool, (
const
unsigned
char
*)
""
), NULL);
else
a2 = ap_pstrcat(r->pool, r->method,
":"
, resp->uri, NULL);
ha2 = ap_md5(r->pool, (
const
unsigned
char
*)a2);
return
ap_md5(r->pool,
(unsigned
char
*)ap_pstrcat(r->pool, ha1,
":"
, resp->nonce,
":"
, resp->nonce_count,
":"
,
resp->cnonce,
":"
,
resp->message_qop,
":"
, ha2,
NULL));
}
static
void
copy_uri_components(uri_components *dst, uri_components *src,
request_rec *r)
{
if
(src->scheme && src->scheme[0] !=
'\0'
)
dst->scheme = src->scheme;
else
dst->scheme = (
char
*)
"http"
;
if
(src->hostname && src->hostname[0] !=
'\0'
) {
dst->hostname = ap_pstrdup(r->pool, src->hostname);
ap_unescape_url(dst->hostname);
}
else
dst->hostname = (
char
*) ap_get_server_name(r);
if
(src->port_str && src->port_str[0] !=
'\0'
)
dst->port = src->port;
else
dst->port = ap_get_server_port(r);
if
(src->path && src->path[0] !=
'\0'
) {
dst->path = ap_pstrdup(r->pool, src->path);
ap_unescape_url(dst->path);
}
else
dst->path = src->path;
if
(src->query && src->query[0] !=
'\0'
) {
dst->query = ap_pstrdup(r->pool, src->query);
ap_unescape_url(dst->query);
}
else
dst->query = src->query;
}
static
int
compare_hostnames(
const
char
*h1,
const
char
*h2)
{
const
char
*dot;
if
(!h1 || h1[0] ==
'\0'
)
return
1;
dot =
strchr
(h1,
'.'
);
if
(dot != NULL)
return
!strcasecmp(h1, h2);
dot =
strchr
(h2,
'.'
);
if
(dot == NULL)
return
!strcasecmp(h1, h2);
else
return
(
strlen
(h1) == (
size_t
) (dot - h2)) && !strncasecmp(h1, h2, dot-h2);
}
static
int
authenticate_digest_user(request_rec *r)
{
digest_config_rec *conf;
digest_header_rec *resp;
request_rec *mainreq;
conn_rec *conn = r->connection;
const
char
*t;
int
res;
if
(!(t = ap_auth_type(r)) || strcasecmp(t,
"Digest"
))
return
DECLINED;
if
(!ap_auth_name(r)) {
ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, r,
"Digest: need AuthName: %s"
, r->uri);
return
SERVER_ERROR;
}
mainreq = r;
while
(mainreq->main != NULL) mainreq = mainreq->main;
while
(mainreq->prev != NULL) mainreq = mainreq->prev;
resp = (digest_header_rec *) ap_get_module_config(mainreq->request_config,
&digest_auth_module);
resp->needed_auth = 1;
conf = (digest_config_rec *) ap_get_module_config(r->per_dir_config,
&digest_auth_module);
if
(resp->auth_hdr_sts != VALID) {
if
(resp->auth_hdr_sts == NOT_DIGEST)
ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, r,
"Digest: client used wrong authentication scheme "
"`%s': %s"
, resp->scheme, r->uri);
else
if
(resp->auth_hdr_sts == INVALID)
ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, r,
"Digest: missing user, realm, nonce, uri, digest, "
"cnonce, or nonce_count in authorization header: %s"
,
r->uri);
note_digest_auth_failure(r, conf, resp, 0);
return
AUTH_REQUIRED;
}
r->connection->user = (
char
*) resp->username;
r->connection->ap_auth_type = (
char
*)
"Digest"
;
if
(
strcmp
(resp->uri, resp->raw_request_uri)) {
uri_components r_uri, d_uri;
copy_uri_components(&r_uri, resp->psd_request_uri, r);
if
(ap_parse_uri_components(r->pool, resp->uri, &d_uri) != HTTP_OK) {
ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, r,
"Digest: invalid uri <%s> in Authorization header"
,
resp->uri);
return
BAD_REQUEST;
}
if
(d_uri.hostname)
ap_unescape_url(d_uri.hostname);
if
(d_uri.path)
ap_unescape_url(d_uri.path);
if
(d_uri.query)
ap_unescape_url(d_uri.query);
if
(r->method_number == M_CONNECT) {
if
(
strcmp
(resp->uri, r_uri.hostinfo)) {
ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, r,
"Digest: uri mismatch - <%s> does not match "
"request-uri <%s>"
, resp->uri, r_uri.hostinfo);
return
BAD_REQUEST;
}
}
else
if
(
!compare_hostnames(d_uri.hostname, r_uri.hostname)
|| (d_uri.port_str && d_uri.port != r_uri.port)
|| (d_uri.hostname && d_uri.hostname[0] !=
'\0'
&& !d_uri.port_str && r_uri.port != ap_default_port(r))
|| (d_uri.path != r_uri.path
&& (!d_uri.path || !r_uri.path
||
strcmp
(d_uri.path, r_uri.path))
&& !(d_uri.path && !r_uri.path && resp->psd_request_uri->hostname
&& d_uri.path[0] ==
'*'
&& d_uri.path[1] ==
'\0'
))
|| (d_uri.query != r_uri.query
&& (!d_uri.query || !r_uri.query
||
strcmp
(d_uri.query, r_uri.query)))
) {
ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, r,
"Digest: uri mismatch - <%s> does not match "
"request-uri <%s>"
, resp->uri, resp->raw_request_uri);
return
BAD_REQUEST;
}
}
if
(resp->opaque && resp->opaque_num == 0) {
ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, r,
"Digest: received invalid opaque - got `%s'"
,
resp->opaque);
note_digest_auth_failure(r, conf, resp, 0);
return
AUTH_REQUIRED;
}
if
(
strcmp
(resp->realm, conf->realm)) {
ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, r,
"Digest: realm mismatch - got `%s' but expected `%s'"
,
resp->realm, conf->realm);
note_digest_auth_failure(r, conf, resp, 0);
return
AUTH_REQUIRED;
}
if
(resp->algorithm != NULL
&& strcasecmp(resp->algorithm,
"MD5"
)
&& strcasecmp(resp->algorithm,
"MD5-sess"
)) {
ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, r,
"Digest: unknown algorithm `%s' received: %s"
,
resp->algorithm, r->uri);
note_digest_auth_failure(r, conf, resp, 0);
return
AUTH_REQUIRED;
}
if
(!conf->pwfile)
return
DECLINED;
if
(!(conf->ha1 = get_hash(r, conn->user, conf->realm, conf->pwfile))) {
ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, r,
"Digest: user `%s' in realm `%s' not found: %s"
,
conn->user, conf->realm, r->uri);
note_digest_auth_failure(r, conf, resp, 0);
return
AUTH_REQUIRED;
}
if
(resp->message_qop == NULL) {
if
(
strcmp
(resp->digest, old_digest(r, resp, conf->ha1))) {
ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, r,
"Digest: user %s: password mismatch: %s"
, conn->user,
r->uri);
note_digest_auth_failure(r, conf, resp, 0);
return
AUTH_REQUIRED;
}
}
else
{
const
char
*exp_digest;
int
match = 0, idx;
for
(idx=0; conf->qop_list[idx] != NULL; idx++) {
if
(!strcasecmp(conf->qop_list[idx], resp->message_qop)) {
match = 1;
break
;
}
}
if
(!match
&& !(conf->qop_list[0] == NULL
&& !strcasecmp(resp->message_qop,
"auth"
))) {
ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, r,
"Digest: invalid qop `%s' received: %s"
,
resp->message_qop, r->uri);
note_digest_auth_failure(r, conf, resp, 0);
return
AUTH_REQUIRED;
}
if
(check_nc(r, resp, conf) != OK) {
note_digest_auth_failure(r, conf, resp, 0);
return
AUTH_REQUIRED;
}
exp_digest = new_digest(r, resp, conf);
if
(!exp_digest) {
return
SERVER_ERROR;
}
if
(
strcmp
(resp->digest, exp_digest)) {
ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, r,
"Digest: user %s: password mismatch: %s"
, conn->user,
r->uri);
note_digest_auth_failure(r, conf, resp, 0);
return
AUTH_REQUIRED;
}
}
if
((res = check_nonce(r, resp, conf)))
return
res;
return
OK;
}
static
table *groups_for_user(request_rec *r,
const
char
*user,
const
char
*grpfile)
{
configfile_t *f;
table *grps = ap_make_table(r->pool, 15);
pool *sp;
char
l[MAX_STRING_LEN];
const
char
*group_name, *ll, *w;
if
(!(f = ap_pcfg_openfile(r->pool, grpfile))) {
ap_log_rerror(APLOG_MARK, APLOG_ERR, r,
"Digest: Could not open group file: %s"
, grpfile);
return
NULL;
}
sp = ap_make_sub_pool(r->pool);
while
(!(ap_cfg_getline(l, MAX_STRING_LEN, f))) {
if
((l[0] ==
'#'
) || (!l[0]))
continue
;
ll = l;
ap_clear_pool(sp);
group_name = ap_getword(sp, &ll,
':'
);
while
(ll[0]) {
w = ap_getword_conf(sp, &ll);
if
(!
strcmp
(w, user)) {
ap_table_setn(grps, ap_pstrdup(r->pool, group_name),
"in"
);
break
;
}
}
}
ap_cfg_closefile(f);
ap_destroy_pool(sp);
return
grps;
}
static
int
digest_check_auth(request_rec *r)
{
const
digest_config_rec *conf =
(digest_config_rec *) ap_get_module_config(r->per_dir_config,
&digest_auth_module);
const
char
*user = r->connection->user;
int
m = r->method_number;
int
method_restricted = 0;
register
int
x;
const
char
*t, *w;
table *grpstatus;
const
array_header *reqs_arr;
require_line *reqs;
if
(!(t = ap_auth_type(r)) || strcasecmp(t,
"Digest"
))
return
DECLINED;
reqs_arr = ap_requires(r);
if
(!reqs_arr)
return
OK;
reqs = (require_line *) reqs_arr->elts;
if
(conf->grpfile)
grpstatus = groups_for_user(r, user, conf->grpfile);
else
grpstatus = NULL;
for
(x = 0; x < reqs_arr->nelts; x++) {
if
(!(reqs[x].method_mask & (1 << m)))
continue
;
method_restricted = 1;
t = reqs[x].requirement;
w = ap_getword_white(r->pool, &t);
if
(!strcasecmp(w,
"valid-user"
))
return
OK;
else
if
(!strcasecmp(w,
"user"
)) {
while
(t[0]) {
w = ap_getword_conf(r->pool, &t);
if
(!
strcmp
(user, w))
return
OK;
}
}
else
if
(!strcasecmp(w,
"group"
)) {
if
(!grpstatus)
return
DECLINED;
while
(t[0]) {
w = ap_getword_conf(r->pool, &t);
if
(ap_table_get(grpstatus, w))
return
OK;
}
}
else
{
ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, r,
"Digest: access to %s failed, reason: unknown require "
"directive \"%s\""
, r->uri, reqs[x].requirement);
return
DECLINED;
}
}
if
(!method_restricted)
return
OK;
ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, r,
"Digest: access to %s failed, reason: user %s not allowed access"
,
r->uri, user);
note_digest_auth_failure(r, conf,
(digest_header_rec *) ap_get_module_config(r->request_config,
&digest_auth_module),
0);
return
AUTH_REQUIRED;
}
#ifdef SEND_DIGEST
static
const
char
*hdr(
const
table *tbl,
const
char
*name)
{
const
char
*val = ap_table_get(tbl, name);
if
(val)
return
val;
else
return
""
;
}
#endif
static
int
add_auth_info(request_rec *r)
{
const
digest_config_rec *conf =
(digest_config_rec *) ap_get_module_config(r->per_dir_config,
&digest_auth_module);
digest_header_rec *resp =
(digest_header_rec *) ap_get_module_config(r->request_config,
&digest_auth_module);
const
char
*ai = NULL, *digest = NULL, *nextnonce =
""
;
if
(resp == NULL || !resp->needed_auth || conf == NULL)
return
OK;
if
(resp->message_qop == NULL) {
#ifdef SEND_DIGEST
char
*entity_info =
ap_md5(r->pool,
(unsigned
char
*) ap_pstrcat(r->pool, resp->raw_request_uri,
":"
,
r->content_type ? r->content_type : ap_default_type(r),
":"
,
hdr(r->headers_out,
"Content-Length"
),
":"
,
r->content_encoding ? r->content_encoding :
""
,
":"
,
hdr(r->headers_out,
"Last-Modified"
),
":"
,
r->no_cache && !ap_table_get(r->headers_out,
"Expires"
) ?
ap_gm_timestr_822(r->pool, r->request_time) :
hdr(r->headers_out,
"Expires"
),
NULL));
digest =
ap_md5(r->pool,
(unsigned
char
*)ap_pstrcat(r->pool, conf->ha1,
":"
,
resp->nonce,
":"
,
r->method,
":"
,
ap_gm_timestr_822(r->pool, r->request_time),
":"
,
entity_info,
":"
,
ap_md5(r->pool, (unsigned
char
*)
""
),
NULL));
#endif
}
if
(conf->nonce_lifetime > 0) {
if
(
difftime
(r->request_time, resp->nonce_time) > (conf->nonce_lifetime-NEXTNONCE_DELTA)) {
nextnonce = ap_pstrcat(r->pool,
", nextnonce=\""
,
gen_nonce(r->pool, r->request_time,
resp->opaque, r->server, conf),
"\""
, NULL);
if
(resp->client)
resp->client->nonce_count = 0;
}
}
else
if
(conf->nonce_lifetime == 0 && resp->client) {
const
char
*nonce = gen_nonce(r->pool, 0, resp->opaque, r->server,
conf);
nextnonce = ap_pstrcat(r->pool,
", nextnonce=\""
, nonce,
"\""
, NULL);
memcpy
(resp->client->last_nonce, nonce, NONCE_LEN+1);
}
if
(conf->qop_list[0] && !strcasecmp(conf->qop_list[0],
"none"
)
&& resp->message_qop == NULL) {
if
(digest)
ai = ap_pstrcat(r->pool,
"digest=\""
, digest,
"\""
, nextnonce,NULL);
else
ai = nextnonce;
}
else
{
const
char
*resp_dig, *ha1, *a2, *ha2;
if
(resp->algorithm && !strcasecmp(resp->algorithm,
"MD5-sess"
)) {
ha1 = get_session_HA1(r, resp, conf, 0);
if
(!ha1) {
ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, r,
"Digest: internal error: couldn't find session "
"info for user %s"
, resp->username);
return
!OK;
}
}
else
ha1 = conf->ha1;
if
(resp->message_qop && !strcasecmp(resp->message_qop,
"auth-int"
))
a2 = ap_pstrcat(r->pool,
":"
, resp->uri,
":"
,
ap_md5(r->pool, (
const
unsigned
char
*)
""
), NULL);
else
a2 = ap_pstrcat(r->pool,
":"
, resp->uri, NULL);
ha2 = ap_md5(r->pool, (
const
unsigned
char
*)a2);
resp_dig = ap_md5(r->pool,
(unsigned
char
*)ap_pstrcat(r->pool, ha1,
":"
,
resp->nonce,
":"
,
resp->nonce_count,
":"
,
resp->cnonce,
":"
,
resp->message_qop ?
resp->message_qop :
""
,
":"
, ha2, NULL));
ai = ap_pstrcat(r->pool,
"rspauth=\""
, resp_dig,
"\""
,
nextnonce,
resp->cnonce ?
", cnonce=\""
:
""
,
resp->cnonce ? ap_escape_quotes(r->pool, resp->cnonce) :
""
,
resp->cnonce ?
"\""
:
""
,
resp->nonce_count ?
", nc="
:
""
,
resp->nonce_count ? resp->nonce_count :
""
,
resp->message_qop ?
", qop="
:
""
,
resp->message_qop ? resp->message_qop :
""
,
digest ?
"digest=\""
:
""
,
digest ? digest :
""
,
digest ?
"\""
:
""
,
NULL);
}
if
(ai && ai[0])
ap_table_mergen(r->headers_out,
r->proxyreq == STD_PROXY ?
"Proxy-Authentication-Info"
:
"Authentication-Info"
,
ai);
return
OK;
}
module MODULE_VAR_EXPORT digest_auth_module =
{
STANDARD_MODULE_STUFF,
initialize_module,
create_digest_dir_config,
NULL,
NULL,
NULL,
digest_cmds,
NULL,
NULL,
authenticate_digest_user,
digest_check_auth,
NULL,
NULL,
add_auth_info,
NULL,
NULL,
NULL,
NULL,
update_nonce_count
};