@ -5,6 +5,10 @@
# include "log.h"
# include "response.h"
# include "inet_ntop_cache.h"
# include "base64.h"
# include "md5.h"
# include <sys/types.h>
# include <sys/stat.h>
@ -179,6 +183,485 @@ static int mod_auth_patch_connection(server *srv, connection *con, mod_auth_plug
}
# undef PATCH
static int mod_auth_match_rules ( server * srv , array * req , const char * username , const char * group , const char * host ) {
const char * r = NULL , * rules = NULL ;
int username_len ;
data_string * require ;
UNUSED ( group ) ;
UNUSED ( host ) ;
require = ( data_string * ) array_get_element ( req , " require " ) ;
if ( ! require ) return - 1 ; /*(should not happen; config is validated at startup)*/
/* if we get here, the user we got a authed user */
if ( 0 = = strcmp ( require - > value - > ptr , " valid-user " ) ) {
return 0 ;
}
/* user=name1|group=name3|host=name4 */
/* seperate the string by | */
#if 0
log_error_write ( srv , __FILE__ , __LINE__ , " sb " , " rules " , require - > value ) ;
# endif
username_len = username ? strlen ( username ) : 0 ;
r = rules = require - > value - > ptr ;
while ( 1 ) {
const char * eq ;
const char * k , * v , * e ;
int k_len , v_len , r_len ;
e = strchr ( r , ' | ' ) ;
if ( e ) {
r_len = e - r ;
} else {
r_len = strlen ( rules ) - ( r - rules ) ;
}
/* from r to r + r_len is a rule */
if ( 0 = = strncmp ( r , " valid-user " , r_len ) ) {
log_error_write ( srv , __FILE__ , __LINE__ , " sb " ,
" parsing the 'require' section in 'auth.require' failed: valid-user cannot be combined with other require rules " ,
require - > value ) ;
return - 1 ;
}
/* search for = in the rules */
if ( NULL = = ( eq = strchr ( r , ' = ' ) ) ) {
log_error_write ( srv , __FILE__ , __LINE__ , " sb " ,
" parsing the 'require' section in 'auth.require' failed: a = is missing " ,
require - > value ) ;
return - 1 ;
}
/* = out of range */
if ( eq > r + r_len ) {
log_error_write ( srv , __FILE__ , __LINE__ , " sb " ,
" parsing the 'require' section in 'auth.require' failed: = out of range " ,
require - > value ) ;
return - 1 ;
}
/* the part before the = is user|group|host */
k = r ;
k_len = eq - r ;
v = eq + 1 ;
v_len = r_len - k_len - 1 ;
if ( k_len = = 4 ) {
if ( 0 = = strncmp ( k , " user " , k_len ) ) {
if ( username & &
username_len = = v_len & &
0 = = strncmp ( username , v , v_len ) ) {
return 0 ;
}
} else if ( 0 = = strncmp ( k , " host " , k_len ) ) {
log_error_write ( srv , __FILE__ , __LINE__ , " s " , " host ... (not implemented) " ) ;
} else {
log_error_write ( srv , __FILE__ , __LINE__ , " s " , " unknown key " ) ;
return - 1 ;
}
} else if ( k_len = = 5 ) {
if ( 0 = = strncmp ( k , " group " , k_len ) ) {
log_error_write ( srv , __FILE__ , __LINE__ , " s " , " group ... (not implemented) " ) ;
} else {
log_error_write ( srv , __FILE__ , __LINE__ , " ss " , " unknown key " , k ) ;
return - 1 ;
}
} else {
log_error_write ( srv , __FILE__ , __LINE__ , " s " , " unknown key " ) ;
return - 1 ;
}
if ( ! e ) break ;
r = e + 1 ;
}
log_error_write ( srv , __FILE__ , __LINE__ , " s " , " nothing matched " ) ;
return - 1 ;
}
static int mod_auth_basic_check ( server * srv , connection * con , mod_auth_plugin_data * p , array * req , const char * realm_str ) {
buffer * username ;
char * pw ;
data_string * realm ;
realm = ( data_string * ) array_get_element ( req , " realm " ) ;
if ( ! realm ) return 0 ; /*(should not happen; config is validated at startup)*/
username = buffer_init ( ) ;
if ( ! buffer_append_base64_decode ( username , realm_str , strlen ( realm_str ) , BASE64_STANDARD ) ) {
log_error_write ( srv , __FILE__ , __LINE__ , " sb " , " decodeing base64-string failed " , username ) ;
buffer_free ( username ) ;
return 0 ;
}
/* r2 == user:password */
if ( NULL = = ( pw = strchr ( username - > ptr , ' : ' ) ) ) {
log_error_write ( srv , __FILE__ , __LINE__ , " sb " , " : is missing in " , username ) ;
buffer_free ( username ) ;
return 0 ;
}
buffer_string_set_length ( username , pw - username - > ptr ) ;
pw + + ;
/* password doesn't match */
if ( http_auth_basic_password_compare ( srv , p , username , realm - > value , pw ) ) {
log_error_write ( srv , __FILE__ , __LINE__ , " sbsBss " , " password doesn't match for " , con - > uri . path , " username: " , username , " , IP: " , inet_ntop_cache_get_ip ( srv , & ( con - > dst_addr ) ) ) ;
buffer_free ( username ) ;
return 0 ;
}
/* value is our allow-rules */
if ( mod_auth_match_rules ( srv , req , username - > ptr , NULL , NULL ) ) {
buffer_free ( username ) ;
log_error_write ( srv , __FILE__ , __LINE__ , " s " , " rules didn't match " ) ;
return 0 ;
}
/* remember the username */
buffer_copy_buffer ( p - > auth_user , username ) ;
buffer_free ( username ) ;
return 1 ;
}
# define HASHLEN 16
# define HASHHEXLEN 32
typedef unsigned char HASH [ HASHLEN ] ;
typedef char HASHHEX [ HASHHEXLEN + 1 ] ;
static void CvtHex ( const HASH Bin , char ( * Hex ) [ 33 ] ) {
li_tohex ( * Hex , sizeof ( * Hex ) , ( const char * ) Bin , 16 ) ;
}
typedef struct {
const char * key ;
int key_len ;
char * * ptr ;
} digest_kv ;
/* return values: -1: error/bad request, 0: failed, 1: success */
static int mod_auth_digest_check ( server * srv , connection * con , mod_auth_plugin_data * p , array * req , const char * realm_str ) {
char a1 [ 33 ] ;
char a2 [ 33 ] ;
char * username = NULL ;
char * realm = NULL ;
char * nonce = NULL ;
char * uri = NULL ;
char * algorithm = NULL ;
char * qop = NULL ;
char * cnonce = NULL ;
char * nc = NULL ;
char * respons = NULL ;
char * e , * c ;
const char * m = NULL ;
int i ;
buffer * b ;
li_MD5_CTX Md5Ctx ;
HASH HA1 ;
HASH HA2 ;
HASH RespHash ;
HASHHEX HA2Hex ;
/* init pointers */
# define S(x) \
x , sizeof ( x ) - 1 , NULL
digest_kv dkv [ 10 ] = {
{ S ( " username= " ) } ,
{ S ( " realm= " ) } ,
{ S ( " nonce= " ) } ,
{ S ( " uri= " ) } ,
{ S ( " algorithm= " ) } ,
{ S ( " qop= " ) } ,
{ S ( " cnonce= " ) } ,
{ S ( " nc= " ) } ,
{ S ( " response= " ) } ,
{ NULL , 0 , NULL }
} ;
# undef S
dkv [ 0 ] . ptr = & username ;
dkv [ 1 ] . ptr = & realm ;
dkv [ 2 ] . ptr = & nonce ;
dkv [ 3 ] . ptr = & uri ;
dkv [ 4 ] . ptr = & algorithm ;
dkv [ 5 ] . ptr = & qop ;
dkv [ 6 ] . ptr = & cnonce ;
dkv [ 7 ] . ptr = & nc ;
dkv [ 8 ] . ptr = & respons ;
b = buffer_init_string ( realm_str ) ;
/* parse credentials from client */
for ( c = b - > ptr ; * c ; c + + ) {
/* skip whitespaces */
while ( * c = = ' ' | | * c = = ' \t ' ) c + + ;
if ( ! * c ) break ;
for ( i = 0 ; dkv [ i ] . key ; i + + ) {
if ( ( 0 = = strncmp ( c , dkv [ i ] . key , dkv [ i ] . key_len ) ) ) {
if ( ( c [ dkv [ i ] . key_len ] = = ' " ' ) & &
( NULL ! = ( e = strchr ( c + dkv [ i ] . key_len + 1 , ' " ' ) ) ) ) {
/* value with "..." */
* ( dkv [ i ] . ptr ) = c + dkv [ i ] . key_len + 1 ;
c = e ;
* e = ' \0 ' ;
} else if ( NULL ! = ( e = strchr ( c + dkv [ i ] . key_len , ' , ' ) ) ) {
/* value without "...", terminated by ',' */
* ( dkv [ i ] . ptr ) = c + dkv [ i ] . key_len ;
c = e ;
* e = ' \0 ' ;
} else {
/* value without "...", terminated by EOL */
* ( dkv [ i ] . ptr ) = c + dkv [ i ] . key_len ;
c + = strlen ( c ) - 1 ;
}
break ;
}
}
}
if ( p - > conf . auth_debug > 1 ) {
log_error_write ( srv , __FILE__ , __LINE__ , " ss " , " username " , username ) ;
log_error_write ( srv , __FILE__ , __LINE__ , " ss " , " realm " , realm ) ;
log_error_write ( srv , __FILE__ , __LINE__ , " ss " , " nonce " , nonce ) ;
log_error_write ( srv , __FILE__ , __LINE__ , " ss " , " uri " , uri ) ;
log_error_write ( srv , __FILE__ , __LINE__ , " ss " , " algorithm " , algorithm ) ;
log_error_write ( srv , __FILE__ , __LINE__ , " ss " , " qop " , qop ) ;
log_error_write ( srv , __FILE__ , __LINE__ , " ss " , " cnonce " , cnonce ) ;
log_error_write ( srv , __FILE__ , __LINE__ , " ss " , " nc " , nc ) ;
log_error_write ( srv , __FILE__ , __LINE__ , " ss " , " response " , respons ) ;
}
/* check if everything is transmitted */
if ( ! username | |
! realm | |
! nonce | |
! uri | |
( qop & & ( ! nc | | ! cnonce ) ) | |
! respons ) {
/* missing field */
log_error_write ( srv , __FILE__ , __LINE__ , " s " ,
" digest: missing field " ) ;
buffer_free ( b ) ;
return - 1 ;
}
/**
* protect the md5 - sess against missing cnonce and nonce
*/
if ( algorithm & &
0 = = strcasecmp ( algorithm , " md5-sess " ) & &
( ! nonce | | ! cnonce ) ) {
log_error_write ( srv , __FILE__ , __LINE__ , " s " ,
" digest: (md5-sess: missing field " ) ;
buffer_free ( b ) ;
return - 1 ;
}
if ( qop & & strcasecmp ( qop , " auth-int " ) = = 0 ) {
log_error_write ( srv , __FILE__ , __LINE__ , " s " ,
" digest: qop=auth-int not supported " ) ;
buffer_free ( b ) ;
return - 1 ;
}
m = get_http_method_name ( con - > request . http_method ) ;
force_assert ( m ) ;
/* detect if attacker is attempting to reuse valid digest for one uri
* on a different request uri . Might also happen if intermediate proxy
* altered client request line . ( Altered request would not result in
* the same digest as that calculated by the client . )
* Internal redirects such as with mod_rewrite will modify request uri .
* Reauthentication is done to detect crossing auth realms , but this
* uri validation step is bypassed . con - > request . orig_uri is original
* uri sent in client request . */
{
const size_t ulen = strlen ( uri ) ;
const size_t rlen = buffer_string_length ( con - > request . orig_uri ) ;
if ( ! buffer_is_equal_string ( con - > request . orig_uri , uri , ulen )
& & ! ( rlen < ulen & & 0 = = memcmp ( con - > request . orig_uri - > ptr , uri , rlen ) & & uri [ rlen ] = = ' ? ' ) ) {
log_error_write ( srv , __FILE__ , __LINE__ , " sbssss " ,
" digest: auth failed: uri mismatch ( " , con - > request . orig_uri , " != " , uri , " ), IP: " , inet_ntop_cache_get_ip ( srv , & ( con - > dst_addr ) ) ) ;
buffer_free ( b ) ;
return - 1 ;
}
}
/* password-string == HA1 */
i = http_auth_get_password_digest ( srv , p , username , realm , HA1 ) ;
if ( 1 ! = i ) {
buffer_free ( b ) ;
return i ;
}
if ( algorithm & &
strcasecmp ( algorithm , " md5-sess " ) = = 0 ) {
li_MD5_Init ( & Md5Ctx ) ;
/* Errata ID 1649: http://www.rfc-editor.org/errata_search.php?rfc=2617 */
CvtHex ( HA1 , & a1 ) ;
li_MD5_Update ( & Md5Ctx , ( unsigned char * ) a1 , HASHHEXLEN ) ;
li_MD5_Update ( & Md5Ctx , CONST_STR_LEN ( " : " ) ) ;
li_MD5_Update ( & Md5Ctx , ( unsigned char * ) nonce , strlen ( nonce ) ) ;
li_MD5_Update ( & Md5Ctx , CONST_STR_LEN ( " : " ) ) ;
li_MD5_Update ( & Md5Ctx , ( unsigned char * ) cnonce , strlen ( cnonce ) ) ;
li_MD5_Final ( HA1 , & Md5Ctx ) ;
}
CvtHex ( HA1 , & a1 ) ;
/* calculate H(A2) */
li_MD5_Init ( & Md5Ctx ) ;
li_MD5_Update ( & Md5Ctx , ( unsigned char * ) m , strlen ( m ) ) ;
li_MD5_Update ( & Md5Ctx , CONST_STR_LEN ( " : " ) ) ;
li_MD5_Update ( & Md5Ctx , ( unsigned char * ) uri , strlen ( uri ) ) ;
/* qop=auth-int not supported, already checked above */
/*
if ( qop & & strcasecmp ( qop , " auth-int " ) = = 0 ) {
li_MD5_Update ( & Md5Ctx , CONST_STR_LEN ( " : " ) ) ;
li_MD5_Update ( & Md5Ctx , ( unsigned char * ) [ body checksum ] , HASHHEXLEN ) ;
}
*/
li_MD5_Final ( HA2 , & Md5Ctx ) ;
CvtHex ( HA2 , & HA2Hex ) ;
/* calculate response */
li_MD5_Init ( & Md5Ctx ) ;
li_MD5_Update ( & Md5Ctx , ( unsigned char * ) a1 , HASHHEXLEN ) ;
li_MD5_Update ( & Md5Ctx , CONST_STR_LEN ( " : " ) ) ;
li_MD5_Update ( & Md5Ctx , ( unsigned char * ) nonce , strlen ( nonce ) ) ;
li_MD5_Update ( & Md5Ctx , CONST_STR_LEN ( " : " ) ) ;
if ( qop & & * qop ) {
li_MD5_Update ( & Md5Ctx , ( unsigned char * ) nc , strlen ( nc ) ) ;
li_MD5_Update ( & Md5Ctx , CONST_STR_LEN ( " : " ) ) ;
li_MD5_Update ( & Md5Ctx , ( unsigned char * ) cnonce , strlen ( cnonce ) ) ;
li_MD5_Update ( & Md5Ctx , CONST_STR_LEN ( " : " ) ) ;
li_MD5_Update ( & Md5Ctx , ( unsigned char * ) qop , strlen ( qop ) ) ;
li_MD5_Update ( & Md5Ctx , CONST_STR_LEN ( " : " ) ) ;
} ;
li_MD5_Update ( & Md5Ctx , ( unsigned char * ) HA2Hex , HASHHEXLEN ) ;
li_MD5_Final ( RespHash , & Md5Ctx ) ;
CvtHex ( RespHash , & a2 ) ;
if ( 0 ! = strcmp ( a2 , respons ) ) {
/* digest not ok */
if ( p - > conf . auth_debug ) {
log_error_write ( srv , __FILE__ , __LINE__ , " sss " ,
" digest: digest mismatch " , a2 , respons ) ;
}
log_error_write ( srv , __FILE__ , __LINE__ , " ssss " ,
" digest: auth failed for " , username , " : wrong password, IP: " , inet_ntop_cache_get_ip ( srv , & ( con - > dst_addr ) ) ) ;
buffer_free ( b ) ;
return 0 ;
}
/* value is our allow-rules */
if ( mod_auth_match_rules ( srv , req , username , NULL , NULL ) ) {
buffer_free ( b ) ;
if ( p - > conf . auth_debug ) {
log_error_write ( srv , __FILE__ , __LINE__ , " s " ,
" digest: rules did match " ) ;
}
return 0 ;
}
/* check age of nonce. Note that rand() is used in nonce generation
* in mod_auth_digest_generate_nonce ( ) . If that were replaced
* with nanosecond time , then nonce secret would remain unique enough
* for the purposes of Digest auth , and would be reproducible ( and
* verifiable ) if nanoseconds were inclued with seconds as part of the
* nonce " timestamp:secret " . Since that is not done , timestamp in
* nonce could theoretically be modified and still produce same md5sum ,
* but that is highly unlikely within a 10 min ( moving ) window of valid
* time relative to current time ( now ) */
{
time_t ts = 0 ;
const unsigned char * const nonce_uns = ( unsigned char * ) nonce ;
for ( i = 0 ; i < 8 & & light_isxdigit ( nonce_uns [ i ] ) ; + + i ) {
ts = ( ts < < 4 ) + hex2int ( nonce_uns [ i ] ) ;
}
if ( i ! = 8 | | nonce [ 8 ] ! = ' : '
| | ts > srv - > cur_ts | | srv - > cur_ts - ts > 600 ) { /*(10 mins)*/
buffer_free ( b ) ;
return - 2 ; /* nonce is stale; have client regenerate digest */
} /*(future: might send nextnonce when expiration is imminent)*/
}
/* remember the username */
buffer_copy_string ( p - > auth_user , username ) ;
buffer_free ( b ) ;
if ( p - > conf . auth_debug ) {
log_error_write ( srv , __FILE__ , __LINE__ , " s " ,
" digest: auth ok " ) ;
}
return 1 ;
}
static int mod_auth_digest_generate_nonce ( server * srv , mod_auth_plugin_data * p , buffer * fn , char ( * out ) [ 33 ] ) {
HASH h ;
li_MD5_CTX Md5Ctx ;
char hh [ LI_ITOSTRING_LENGTH ] ;
UNUSED ( p ) ;
/* generate shared-secret */
li_MD5_Init ( & Md5Ctx ) ;
li_MD5_Update ( & Md5Ctx , CONST_BUF_LEN ( fn ) ) ;
li_MD5_Update ( & Md5Ctx , CONST_STR_LEN ( " + " ) ) ;
/* we assume sizeof(time_t) == 4 here, but if not it ain't a problem at all */
li_itostrn ( hh , sizeof ( hh ) , srv - > cur_ts ) ;
li_MD5_Update ( & Md5Ctx , ( unsigned char * ) hh , strlen ( hh ) ) ;
li_MD5_Update ( & Md5Ctx , ( unsigned char * ) srv - > entropy , sizeof ( srv - > entropy ) ) ;
li_itostrn ( hh , sizeof ( hh ) , rand ( ) ) ;
li_MD5_Update ( & Md5Ctx , ( unsigned char * ) hh , strlen ( hh ) ) ;
li_MD5_Final ( h , & Md5Ctx ) ;
CvtHex ( h , out ) ;
return 0 ;
}
static handler_t mod_auth_uri_handler ( server * srv , connection * con , void * p_d ) {
size_t k ;
int auth_required = 0 , auth_satisfied = 0 ;
@ -238,7 +721,7 @@ static handler_t mod_auth_uri_handler(server *srv, connection *con, void *p_d) {
con - > http_status = 401 ;
con - > mode = DIRECT ;
return HANDLER_FINISHED ;
} else if ( http _auth_match_rules( srv , req , ds - > value - > ptr , NULL , NULL ) ) {
} else if ( mod _auth_match_rules( srv , req , ds - > value - > ptr , NULL , NULL ) ) {
log_error_write ( srv , __FILE__ , __LINE__ , " s " , " rules didn't match " ) ;
con - > http_status = 401 ;
con - > mode = DIRECT ;
@ -264,13 +747,13 @@ static handler_t mod_auth_uri_handler(server *srv, connection *con, void *p_d) {
auth_type = " Basic " ;
if ( 0 = = strcmp ( req_method - > value - > ptr , " basic " ) ) {
auth_satisfied = http _auth_basic_check( srv , con , p , req , auth_realm + 1 ) ;
auth_satisfied = mod _auth_basic_check( srv , con , p , req , auth_realm + 1 ) ;
}
} else if ( ( auth_type_len = = 6 ) & &
( 0 = = strncasecmp ( http_authorization , " Digest " , auth_type_len ) ) ) {
auth_type = " Digest " ;
if ( 0 = = strcmp ( req_method - > value - > ptr , " digest " ) ) {
if ( - 1 = = ( auth_satisfied = http _auth_digest_check( srv , con , p , req , auth_realm + 1 ) ) ) {
if ( - 1 = = ( auth_satisfied = mod _auth_digest_check( srv , con , p , req , auth_realm + 1 ) ) ) {
con - > http_status = 400 ;
con - > mode = DIRECT ;
@ -306,7 +789,7 @@ static handler_t mod_auth_uri_handler(server *srv, connection *con, void *p_d) {
response_header_insert ( srv , con , CONST_STR_LEN ( " WWW-Authenticate " ) , CONST_BUF_LEN ( p - > tmp_buf ) ) ;
} else if ( 0 = = strcmp ( method - > value - > ptr , " digest " ) ) {
char hh [ 33 ] ;
http _auth_digest_generate_nonce( srv , p , srv - > tmp_buf , & hh ) ;
mod _auth_digest_generate_nonce( srv , p , srv - > tmp_buf , & hh ) ;
buffer_copy_string_len ( p - > tmp_buf , CONST_STR_LEN ( " Digest realm= \" " ) ) ;
buffer_append_string_buffer ( p - > tmp_buf , realm - > value ) ;