2013-08-10 07:46:45 +00:00
# pragma once
# include <string.h>
# include <Poco/RegularExpression.h>
# include <Poco/Net/IPAddress.h>
2013-08-11 00:07:49 +00:00
# include <Poco/Net/SocketAddress.h>
2013-08-10 07:46:45 +00:00
# include <Poco/Net/DNS.h>
# include <Poco/Util/Application.h>
# include <Poco/Util/AbstractConfiguration.h>
2015-09-24 07:18:05 +00:00
# include <Poco/String.h>
2013-08-10 07:46:45 +00:00
# include <DB/Core/Types.h>
2015-10-05 01:35:28 +00:00
# include <DB/Common/Exception.h>
2013-08-10 07:46:45 +00:00
# include <DB/IO/ReadHelpers.h>
2015-09-24 07:18:05 +00:00
# include <DB/IO/HexWriteBuffer.h>
# include <DB/IO/WriteBufferFromString.h>
2015-09-27 14:28:43 +00:00
# include <DB/IO/WriteHelpers.h>
2015-09-27 02:18:00 +00:00
# include <DB/Common/SimpleCache.h>
2016-07-14 05:22:09 +00:00
# include <DB/Common/StringUtils.h>
2015-09-24 07:18:05 +00:00
# include <openssl/sha.h>
2013-08-10 07:46:45 +00:00
2015-09-29 19:19:54 +00:00
# include <common/logger_useful.h>
2013-12-26 09:42:32 +00:00
2015-10-01 15:10:41 +00:00
# include <unordered_set>
2013-08-10 07:46:45 +00:00
namespace DB
{
2016-01-11 21:46:36 +00:00
namespace ErrorCodes
{
extern const int DNS_ERROR ;
extern const int UNKNOWN_ADDRESS_PATTERN_TYPE ;
extern const int UNKNOWN_USER ;
extern const int REQUIRED_PASSWORD ;
extern const int WRONG_PASSWORD ;
extern const int IP_ADDRESS_NOT_ALLOWED ;
}
2013-08-10 07:46:45 +00:00
/// Позволяет проверить соответствие адреса шаблону.
class IAddressPattern
{
public :
virtual bool contains ( const Poco : : Net : : IPAddress & addr ) const = 0 ;
virtual ~ IAddressPattern ( ) { }
static Poco : : Net : : IPAddress toIPv6 ( const Poco : : Net : : IPAddress addr )
{
if ( addr . family ( ) = = Poco : : Net : : IPAddress : : IPv6 )
return addr ;
return Poco : : Net : : IPAddress ( " ::FFFF: " + addr . toString ( ) ) ;
}
} ;
/// IP-адрес или маска подсети. Например, 213.180.204.3 или 10.0.0.1/8 или 2a02:6b8::3 или 2a02:6b8::3/64.
class IPAddressPattern : public IAddressPattern
{
private :
/// Адрес маски. Всегда переводится в IPv6.
Poco : : Net : : IPAddress mask_address ;
/// Количество бит в маске.
UInt8 prefix_bits ;
public :
explicit IPAddressPattern ( const String & str )
{
const char * pos = strchr ( str . c_str ( ) , ' / ' ) ;
2014-04-08 07:47:51 +00:00
if ( nullptr = = pos )
2013-08-10 07:46:45 +00:00
{
construct ( Poco : : Net : : IPAddress ( str ) ) ;
}
else
{
String addr ( str , 0 , pos - str . c_str ( ) ) ;
UInt8 prefix_bits_ = parse < UInt8 > ( pos + 1 ) ;
construct ( Poco : : Net : : IPAddress ( addr ) , prefix_bits_ ) ;
}
}
2015-09-24 07:18:05 +00:00
2013-08-10 07:46:45 +00:00
bool contains ( const Poco : : Net : : IPAddress & addr ) const
{
return prefixBitsEquals ( reinterpret_cast < const char * > ( toIPv6 ( addr ) . addr ( ) ) , reinterpret_cast < const char * > ( mask_address . addr ( ) ) , prefix_bits ) ;
}
private :
void construct ( const Poco : : Net : : IPAddress & mask_address_ )
{
mask_address = toIPv6 ( mask_address_ ) ;
prefix_bits = 128 ;
}
void construct ( const Poco : : Net : : IPAddress & mask_address_ , UInt8 prefix_bits_ )
{
mask_address = toIPv6 ( mask_address_ ) ;
prefix_bits = mask_address_ . family ( ) = = Poco : : Net : : IPAddress : : IPv4
? prefix_bits_ + 96
: prefix_bits_ ;
}
static bool prefixBitsEquals ( const char * lhs , const char * rhs , UInt8 prefix_bits )
{
UInt8 prefix_bytes = prefix_bits / 8 ;
UInt8 remaining_bits = prefix_bits % 8 ;
return 0 = = memcmp ( lhs , rhs , prefix_bytes )
& & ( remaining_bits % 8 = = 0
| | ( lhs [ prefix_bytes ] > > ( 8 - remaining_bits ) ) = = ( rhs [ prefix_bytes ] > > ( 8 - remaining_bits ) ) ) ;
}
} ;
/// Проверяет соответствие адреса одному из адресов хоста.
class HostExactPattern : public IAddressPattern
{
private :
String host ;
2015-09-27 02:18:00 +00:00
static bool containsImpl ( const String & host , const Poco : : Net : : IPAddress & addr )
2013-08-10 07:46:45 +00:00
{
Poco : : Net : : IPAddress addr_v6 = toIPv6 ( addr ) ;
2013-08-11 02:00:13 +00:00
/// Резолвим вручную, потому что в Poco не используется флаг AI_ALL, а он важен.
2014-04-08 07:31:51 +00:00
addrinfo * ai = nullptr ;
2013-08-11 02:00:13 +00:00
addrinfo hints ;
memset ( & hints , 0 , sizeof ( hints ) ) ;
hints . ai_family = AF_UNSPEC ;
hints . ai_flags | = AI_V4MAPPED | AI_ALL ;
2014-04-08 07:58:53 +00:00
int ret = getaddrinfo ( host . c_str ( ) , nullptr , & hints , & ai ) ;
2013-08-11 02:00:13 +00:00
if ( 0 ! = ret )
throw Exception ( " Cannot getaddrinfo: " + std : : string ( gai_strerror ( ret ) ) , ErrorCodes : : DNS_ERROR ) ;
try
{
2014-04-08 07:31:51 +00:00
for ( ; ai ! = nullptr ; ai = ai - > ai_next )
2013-08-11 02:00:13 +00:00
{
if ( ai - > ai_addrlen & & ai - > ai_addr )
{
if ( ai - > ai_family = = AF_INET6 )
{
if ( addr_v6 = = Poco : : Net : : IPAddress (
& reinterpret_cast < sockaddr_in6 * > ( ai - > ai_addr ) - > sin6_addr , sizeof ( in6_addr ) ,
reinterpret_cast < sockaddr_in6 * > ( ai - > ai_addr ) - > sin6_scope_id ) )
{
return true ;
}
}
else if ( ai - > ai_family = = AF_INET )
{
if ( addr_v6 = = toIPv6 ( Poco : : Net : : IPAddress (
& reinterpret_cast < sockaddr_in * > ( ai - > ai_addr ) - > sin_addr , sizeof ( in_addr ) ) ) )
{
return true ;
}
}
}
}
}
catch ( . . . )
{
freeaddrinfo ( ai ) ;
throw ;
}
freeaddrinfo ( ai ) ;
2013-08-10 07:46:45 +00:00
return false ;
}
2015-09-27 02:18:00 +00:00
public :
HostExactPattern ( const String & host_ ) : host ( host_ ) { }
bool contains ( const Poco : : Net : : IPAddress & addr ) const
{
static SimpleCache < decltype ( containsImpl ) , & containsImpl > cache ;
return cache ( host , addr ) ;
}
2013-08-10 07:46:45 +00:00
} ;
/// Проверяет соответствие PTR-записи для адреса регекспу (и дополнительно проверяет, что PTR-запись резолвится обратно в адрес клиента).
class HostRegexpPattern : public IAddressPattern
{
private :
Poco : : RegularExpression host_regexp ;
2015-09-27 02:18:00 +00:00
static String getDomain ( const Poco : : Net : : IPAddress & addr )
2013-08-10 07:46:45 +00:00
{
2013-08-11 00:07:49 +00:00
Poco : : Net : : SocketAddress sock_addr ( addr , 0 ) ;
/// Резолвим вручную, потому что в Poco нет такой функциональности.
char domain [ 1024 ] ;
2014-04-08 07:58:53 +00:00
int gai_errno = getnameinfo ( sock_addr . addr ( ) , sock_addr . length ( ) , domain , sizeof ( domain ) , nullptr , 0 , NI_NAMEREQD ) ;
2013-08-11 00:07:49 +00:00
if ( 0 ! = gai_errno )
2013-08-11 00:48:28 +00:00
throw Exception ( " Cannot getnameinfo: " + std : : string ( gai_strerror ( gai_errno ) ) , ErrorCodes : : DNS_ERROR ) ;
2013-08-11 00:07:49 +00:00
2015-09-27 02:18:00 +00:00
return domain ;
}
public :
HostRegexpPattern ( const String & host_regexp_ ) : host_regexp ( host_regexp_ ) { }
bool contains ( const Poco : : Net : : IPAddress & addr ) const
{
static SimpleCache < decltype ( getDomain ) , & getDomain > cache ;
String domain = cache ( addr ) ;
2013-08-10 07:46:45 +00:00
Poco : : RegularExpression : : Match match ;
2015-09-27 02:18:00 +00:00
if ( host_regexp . match ( domain , match ) & & HostExactPattern ( domain ) . contains ( addr ) )
2013-08-11 00:07:49 +00:00
return true ;
2013-08-10 07:46:45 +00:00
return false ;
}
} ;
class AddressPatterns
{
private :
2016-05-28 10:35:44 +00:00
using Container = std : : vector < std : : unique_ptr < IAddressPattern > > ;
2013-08-10 07:46:45 +00:00
Container patterns ;
public :
bool contains ( const Poco : : Net : : IPAddress & addr ) const
{
for ( size_t i = 0 , size = patterns . size ( ) ; i < size ; + + i )
2013-12-24 14:00:18 +00:00
{
/// если хост не резолвится, то пропустим е г о и попробуем другой
try
{
if ( patterns [ i ] - > contains ( addr ) )
return true ;
}
catch ( const DB : : Exception & e )
{
2013-12-27 18:01:53 +00:00
LOG_WARNING ( & Logger : : get ( " AddressPatterns " ) ,
2013-12-27 17:43:36 +00:00
" Failed to check if pattern contains address " < < addr . toString ( ) < < " . " < < e . displayText ( ) < < " , code = " < < e . code ( ) ) ;
2013-12-24 14:00:18 +00:00
if ( e . code ( ) = = ErrorCodes : : DNS_ERROR )
2013-12-26 09:42:32 +00:00
{
2013-12-24 14:00:18 +00:00
continue ;
2013-12-26 09:42:32 +00:00
}
2013-12-24 14:00:18 +00:00
else
throw ;
}
}
2013-08-10 07:46:45 +00:00
return false ;
}
2014-02-13 07:17:22 +00:00
void addFromConfig ( const String & config_elem , Poco : : Util : : AbstractConfiguration & config )
2013-08-10 07:46:45 +00:00
{
Poco : : Util : : AbstractConfiguration : : Keys config_keys ;
config . keys ( config_elem , config_keys ) ;
for ( Poco : : Util : : AbstractConfiguration : : Keys : : const_iterator it = config_keys . begin ( ) ; it ! = config_keys . end ( ) ; + + it )
{
2015-10-05 01:50:42 +00:00
Container : : value_type pattern ;
2013-08-10 07:46:45 +00:00
String value = config . getString ( config_elem + " . " + * it ) ;
2016-07-14 05:22:09 +00:00
if ( startsWith ( * it , " ip " ) )
2015-10-05 01:50:42 +00:00
pattern . reset ( new IPAddressPattern ( value ) ) ;
2016-07-14 05:22:09 +00:00
else if ( startsWith ( * it , " host_regexp " ) )
2015-10-05 01:50:42 +00:00
pattern . reset ( new HostRegexpPattern ( value ) ) ;
2016-07-14 05:22:09 +00:00
else if ( startsWith ( * it , " host " ) )
2015-10-05 01:50:42 +00:00
pattern . reset ( new HostExactPattern ( value ) ) ;
2013-08-10 07:46:45 +00:00
else
throw Exception ( " Unknown address pattern type: " + * it , ErrorCodes : : UNKNOWN_ADDRESS_PATTERN_TYPE ) ;
2015-09-24 07:18:05 +00:00
2015-10-05 01:52:00 +00:00
patterns . emplace_back ( std : : move ( pattern ) ) ;
2013-08-10 07:46:45 +00:00
}
}
} ;
/** Пользователь и ACL.
*/
struct User
{
String name ;
2015-09-24 07:18:05 +00:00
/// Требуемый пароль. Может храниться либо в открытом виде, либо в виде SHA256.
2013-08-10 07:46:45 +00:00
String password ;
2015-09-24 07:18:05 +00:00
String password_sha256_hex ;
2013-08-10 07:46:45 +00:00
String profile ;
2013-08-12 00:36:18 +00:00
String quota ;
2013-08-10 07:46:45 +00:00
AddressPatterns addresses ;
2015-10-01 15:10:41 +00:00
/// Список разрешённых баз данных.
using DatabaseSet = std : : unordered_set < std : : string > ;
DatabaseSet databases ;
2014-02-13 07:17:22 +00:00
User ( const String & name_ , const String & config_elem , Poco : : Util : : AbstractConfiguration & config )
2013-08-10 07:46:45 +00:00
: name ( name_ )
{
2015-09-24 07:18:05 +00:00
bool has_password = config . has ( config_elem + " .password " ) ;
bool has_password_sha256_hex = config . has ( config_elem + " .password_sha256_hex " ) ;
if ( has_password & & has_password_sha256_hex )
throw Exception ( " Both fields 'password' and 'password_sha256_hex' are specified for user " + name + " . Must be only one of them. " , ErrorCodes : : BAD_ARGUMENTS ) ;
if ( ! has_password & & ! has_password_sha256_hex )
throw Exception ( " Either 'password' or 'password_sha256_hex' must be specified for user " + name + " . " , ErrorCodes : : BAD_ARGUMENTS ) ;
if ( has_password )
password = config . getString ( config_elem + " .password " ) ;
if ( has_password_sha256_hex )
{
password_sha256_hex = Poco : : toLower ( config . getString ( config_elem + " .password_sha256_hex " ) ) ;
if ( password_sha256_hex . size ( ) ! = 64 )
throw Exception ( " password_sha256_hex for user " + name + " has length " + toString ( password_sha256_hex . size ( ) ) + " but must be exactly 64 symbols. " , ErrorCodes : : BAD_ARGUMENTS ) ;
}
2013-08-10 07:46:45 +00:00
profile = config . getString ( config_elem + " .profile " ) ;
2013-08-12 00:36:18 +00:00
quota = config . getString ( config_elem + " .quota " ) ;
2013-08-10 07:46:45 +00:00
2014-02-13 07:17:22 +00:00
addresses . addFromConfig ( config_elem + " .networks " , config ) ;
2015-10-01 15:10:41 +00:00
/// Заполнить список разрешённых баз данных.
const auto config_sub_elem = config_elem + " .allow_databases " ;
if ( config . has ( config_sub_elem ) )
{
Poco : : Util : : AbstractConfiguration : : Keys config_keys ;
config . keys ( config_sub_elem , config_keys ) ;
databases . reserve ( config_keys . size ( ) ) ;
for ( const auto & key : config_keys )
{
const auto database_name = config . getString ( config_sub_elem + " . " + key ) ;
databases . insert ( database_name ) ;
}
}
2013-08-10 07:46:45 +00:00
}
/// Для вставки в контейнер.
User ( ) { }
} ;
/// Известные пользователи.
class Users
{
private :
2016-05-28 10:35:44 +00:00
using Container = std : : map < String , User > ;
2013-08-10 07:46:45 +00:00
Container cont ;
2015-09-24 07:18:05 +00:00
2013-08-10 07:46:45 +00:00
public :
2014-02-13 07:17:22 +00:00
void loadFromConfig ( Poco : : Util : : AbstractConfiguration & config )
2013-08-10 07:46:45 +00:00
{
2014-02-13 07:17:22 +00:00
cont . clear ( ) ;
2013-08-10 07:46:45 +00:00
Poco : : Util : : AbstractConfiguration : : Keys config_keys ;
config . keys ( " users " , config_keys ) ;
for ( Poco : : Util : : AbstractConfiguration : : Keys : : const_iterator it = config_keys . begin ( ) ; it ! = config_keys . end ( ) ; + + it )
2014-02-13 07:17:22 +00:00
cont [ * it ] = User ( * it , " users. " + * it , config ) ;
2013-08-10 07:46:45 +00:00
}
const User & get ( const String & name , const String & password , const Poco : : Net : : IPAddress & address ) const
{
Container : : const_iterator it = cont . find ( name ) ;
if ( cont . end ( ) = = it )
throw Exception ( " Unknown user " + name , ErrorCodes : : UNKNOWN_USER ) ;
if ( ! it - > second . addresses . contains ( address ) )
throw Exception ( " User " + name + " is not allowed to connect from address " + address . toString ( ) , ErrorCodes : : IP_ADDRESS_NOT_ALLOWED ) ;
2015-09-24 07:18:05 +00:00
auto on_wrong_password = [ & ] ( )
2013-08-10 07:46:45 +00:00
{
if ( password . empty ( ) )
throw Exception ( " Password required for user " + name , ErrorCodes : : REQUIRED_PASSWORD ) ;
else
throw Exception ( " Wrong password for user " + name , ErrorCodes : : WRONG_PASSWORD ) ;
2015-09-24 07:18:05 +00:00
} ;
if ( ! it - > second . password_sha256_hex . empty ( ) )
{
unsigned char hash [ 32 ] ;
SHA256_CTX ctx ;
SHA256_Init ( & ctx ) ;
SHA256_Update ( & ctx , reinterpret_cast < const unsigned char * > ( password . data ( ) ) , password . size ( ) ) ;
SHA256_Final ( hash , & ctx ) ;
String hash_hex ;
{
WriteBufferFromString buf ( hash_hex ) ;
HexWriteBuffer hex_buf ( buf ) ;
hex_buf . write ( reinterpret_cast < const char * > ( hash ) , sizeof ( hash ) ) ;
}
Poco : : toLowerInPlace ( hash_hex ) ;
if ( hash_hex ! = it - > second . password_sha256_hex )
on_wrong_password ( ) ;
}
else if ( password ! = it - > second . password )
{
on_wrong_password ( ) ;
2013-08-10 07:46:45 +00:00
}
return it - > second ;
}
2015-10-01 15:10:41 +00:00
/// Проверить, имеет ли заданный клиент доступ к заданной базе данных.
bool isAllowedDatabase ( const std : : string & user_name , const std : : string & database_name ) const
{
auto it = cont . find ( user_name ) ;
if ( it = = cont . end ( ) )
throw Exception ( " Unknown user " + user_name , ErrorCodes : : UNKNOWN_USER ) ;
const auto & user = it - > second ;
return user . databases . empty ( ) | | user . databases . count ( database_name ) ;
}
2013-08-10 07:46:45 +00:00
} ;
}