2018-12-21 11:16:22 -05:00
/*
* Copyright ( c ) 2018 , Salvatore Sanfilippo < antirez at gmail dot com >
* All rights reserved .
*
* Redistribution and use in source and binary forms , with or without
* modification , are permitted provided that the following conditions are met :
*
* * Redistributions of source code must retain the above copyright notice ,
* this list of conditions and the following disclaimer .
* * Redistributions in binary form must reproduce the above copyright
* notice , this list of conditions and the following disclaimer in the
* documentation and / or other materials provided with the distribution .
* * Neither the name of Redis nor the names of its contributors may be used
* to endorse or promote products derived from this software without
* specific prior written permission .
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS " AS IS "
* AND ANY EXPRESS OR IMPLIED WARRANTIES , INCLUDING , BUT NOT LIMITED TO , THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED . IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT , INDIRECT , INCIDENTAL , SPECIAL , EXEMPLARY , OR
* CONSEQUENTIAL DAMAGES ( INCLUDING , BUT NOT LIMITED TO , PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES ; LOSS OF USE , DATA , OR PROFITS ; OR BUSINESS
* INTERRUPTION ) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY , WHETHER IN
* CONTRACT , STRICT LIABILITY , OR TORT ( INCLUDING NEGLIGENCE OR OTHERWISE )
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE , EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE .
*/
# include "server.h"
2019-01-10 10:39:32 -05:00
/* =============================================================================
* Global state for ACLs
* = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = */
rax * Users ; /* Table mapping usernames to user structures. */
2019-02-01 06:20:09 -05:00
user * DefaultUser ; /* Global reference to the default user.
Every new connection is associated to it , if no
AUTH or HELLO is used to authenticate with a
different user . */
list * UsersToLoad ; /* This is a list of users found in the configuration file
that we ' ll need to load in the final stage of Redis
initialization , after all the modules are already
loaded . Every list element is a NULL terminated
array of SDS pointers : the first is the user name ,
all the remaining pointers are ACL rules in the same
format as ACLSetUser ( ) . */
2019-01-10 10:39:32 -05:00
2019-01-23 10:47:29 -05:00
struct ACLCategoryItem {
const char * name ;
uint64_t flag ;
} ACLCommandCategories [ ] = {
{ " keyspace " , CMD_CATEGORY_KEYSPACE } ,
{ " read " , CMD_CATEGORY_READ } ,
{ " write " , CMD_CATEGORY_WRITE } ,
{ " set " , CMD_CATEGORY_SET } ,
{ " sortedset " , CMD_CATEGORY_SORTEDSET } ,
{ " list " , CMD_CATEGORY_LIST } ,
{ " hash " , CMD_CATEGORY_HASH } ,
{ " string " , CMD_CATEGORY_STRING } ,
{ " bitmap " , CMD_CATEGORY_BITMAP } ,
{ " hyperloglog " , CMD_CATEGORY_HYPERLOGLOG } ,
{ " geo " , CMD_CATEGORY_GEO } ,
{ " stream " , CMD_CATEGORY_STREAM } ,
{ " pubsub " , CMD_CATEGORY_PUBSUB } ,
{ " admin " , CMD_CATEGORY_ADMIN } ,
{ " fast " , CMD_CATEGORY_FAST } ,
{ " slow " , CMD_CATEGORY_SLOW } ,
{ " blocking " , CMD_CATEGORY_BLOCKING } ,
{ " dangerous " , CMD_CATEGORY_DANGEROUS } ,
{ " connection " , CMD_CATEGORY_CONNECTION } ,
{ " transaction " , CMD_CATEGORY_TRANSACTION } ,
2019-01-23 10:59:09 -05:00
{ " scripting " , CMD_CATEGORY_SCRIPTING } ,
2019-01-31 10:49:22 -05:00
{ NULL , 0 } /* Terminator. */
} ;
struct ACLUserFlag {
const char * name ;
uint64_t flag ;
} ACLUserFlags [ ] = {
{ " on " , USER_FLAG_ENABLED } ,
{ " off " , USER_FLAG_DISABLED } ,
{ " allkeys " , USER_FLAG_ALLKEYS } ,
{ " allcommands " , USER_FLAG_ALLCOMMANDS } ,
{ " nopass " , USER_FLAG_NOPASS } ,
{ NULL , 0 } /* Terminator. */
2019-01-23 10:47:29 -05:00
} ;
2019-01-30 02:09:05 -05:00
void ACLResetSubcommandsForCommand ( user * u , unsigned long id ) ;
2019-01-31 12:33:14 -05:00
void ACLResetSubcommands ( user * u ) ;
2019-02-07 06:57:21 -05:00
void ACLAddAllowedSubcommand ( user * u , unsigned long id , const char * sub ) ;
2019-01-30 02:09:05 -05:00
2019-01-10 10:35:55 -05:00
/* =============================================================================
* Helper functions for the rest of the ACL implementation
* = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = */
2018-12-21 11:16:22 -05:00
/* Return zero if strings are the same, non-zero if they are not.
* The comparison is performed in a way that prevents an attacker to obtain
* information about the nature of the strings just monitoring the execution
* time of the function .
*
* Note that limiting the comparison length to strings up to 512 bytes we
* can avoid leaking any information about the password length and any
* possible branch misprediction related leak .
*/
int time_independent_strcmp ( char * a , char * b ) {
char bufa [ CONFIG_AUTHPASS_MAX_LEN ] , bufb [ CONFIG_AUTHPASS_MAX_LEN ] ;
/* The above two strlen perform len(a) + len(b) operations where either
* a or b are fixed ( our password ) length , and the difference is only
* relative to the length of the user provided string , so no information
* leak is possible in the following two lines of code . */
unsigned int alen = strlen ( a ) ;
unsigned int blen = strlen ( b ) ;
unsigned int j ;
int diff = 0 ;
/* We can't compare strings longer than our static buffers.
* Note that this will never pass the first test in practical circumstances
* so there is no info leak . */
if ( alen > sizeof ( bufa ) | | blen > sizeof ( bufb ) ) return 1 ;
memset ( bufa , 0 , sizeof ( bufa ) ) ; /* Constant time. */
memset ( bufb , 0 , sizeof ( bufb ) ) ; /* Constant time. */
/* Again the time of the following two copies is proportional to
* len ( a ) + len ( b ) so no info is leaked . */
memcpy ( bufa , a , alen ) ;
memcpy ( bufb , b , blen ) ;
/* Always compare all the chars in the two buffers without
* conditional expressions . */
for ( j = 0 ; j < sizeof ( bufa ) ; j + + ) {
diff | = ( bufa [ j ] ^ bufb [ j ] ) ;
}
/* Length must be equal as well. */
diff | = alen ^ blen ;
return diff ; /* If zero strings are the same. */
}
2019-01-10 10:35:55 -05:00
/* =============================================================================
* Low level ACL API
* = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = */
2019-01-23 10:59:09 -05:00
/* Given the category name the command returns the corresponding flag, or
* zero if there is no match . */
uint64_t ACLGetCommandCategoryFlagByName ( const char * name ) {
for ( int j = 0 ; ACLCommandCategories [ j ] . flag ! = 0 ; j + + ) {
if ( ! strcasecmp ( name , ACLCommandCategories [ j ] . name ) ) {
return ACLCommandCategories [ j ] . flag ;
}
}
return 0 ; /* No match. */
}
2019-01-15 06:58:54 -05:00
/* Method for passwords/pattern comparison used for the user->passwords list
* so that we can search for items with listSearchKey ( ) . */
int ACLListMatchSds ( void * a , void * b ) {
return sdscmp ( a , b ) = = 0 ;
}
2019-01-28 06:11:11 -05:00
/* Method to free list elements from ACL users password/ptterns lists. */
void ACLListFreeSds ( void * item ) {
sdsfree ( item ) ;
}
2019-02-07 06:57:21 -05:00
/* Method to duplicate list elements from ACL users password/ptterns lists. */
void * ACLListDupSds ( void * item ) {
return sdsdup ( item ) ;
}
2019-01-10 11:01:12 -05:00
/* Create a new user with the specified name, store it in the list
* of users ( the Users global radix tree ) , and returns a reference to
* the structure representing the user .
*
* If the user with such name already exists NULL is returned . */
2019-01-11 05:02:55 -05:00
user * ACLCreateUser ( const char * name , size_t namelen ) {
2019-01-10 11:01:12 -05:00
if ( raxFind ( Users , ( unsigned char * ) name , namelen ) ! = raxNotFound ) return NULL ;
user * u = zmalloc ( sizeof ( * u ) ) ;
2019-01-15 12:16:20 -05:00
u - > name = sdsnewlen ( name , namelen ) ;
2019-01-31 10:49:22 -05:00
u - > flags = USER_FLAG_DISABLED ;
2019-01-10 11:01:12 -05:00
u - > allowed_subcommands = NULL ;
u - > passwords = listCreate ( ) ;
2019-01-16 07:50:00 -05:00
u - > patterns = listCreate ( ) ;
2019-01-15 06:58:54 -05:00
listSetMatchMethod ( u - > passwords , ACLListMatchSds ) ;
2019-01-28 06:11:11 -05:00
listSetFreeMethod ( u - > passwords , ACLListFreeSds ) ;
2019-02-07 06:57:21 -05:00
listSetDupMethod ( u - > passwords , ACLListDupSds ) ;
2019-01-16 07:50:00 -05:00
listSetMatchMethod ( u - > patterns , ACLListMatchSds ) ;
2019-01-28 06:11:11 -05:00
listSetFreeMethod ( u - > patterns , ACLListFreeSds ) ;
2019-02-07 06:57:21 -05:00
listSetDupMethod ( u - > patterns , ACLListDupSds ) ;
2019-01-10 11:01:12 -05:00
memset ( u - > allowed_commands , 0 , sizeof ( u - > allowed_commands ) ) ;
raxInsert ( Users , ( unsigned char * ) name , namelen , u , NULL ) ;
return u ;
}
2019-02-06 10:19:17 -05:00
/* This function should be called when we need an unlinked "fake" user
* we can use in order to validate ACL rules or for other similar reasons .
* The user will not get linked to the Users radix tree . The returned
* user should be released with ACLFreeUser ( ) as usually . */
user * ACLCreateUnlinkedUser ( void ) {
char username [ 64 ] ;
for ( int j = 0 ; ; j + + ) {
snprintf ( username , sizeof ( username ) , " __fakeuser:%d__ " , j ) ;
user * fakeuser = ACLCreateUser ( username , strlen ( username ) ) ;
if ( fakeuser = = NULL ) continue ;
int retval = raxRemove ( Users , ( unsigned char * ) username ,
strlen ( username ) , NULL ) ;
serverAssert ( retval ! = 0 ) ;
return fakeuser ;
}
}
2019-01-31 12:33:14 -05:00
/* Release the memory used by the user structure. Note that this function
* will not remove the user from the Users global radix tree . */
void ACLFreeUser ( user * u ) {
sdsfree ( u - > name ) ;
listRelease ( u - > passwords ) ;
listRelease ( u - > patterns ) ;
ACLResetSubcommands ( u ) ;
zfree ( u ) ;
}
2019-02-07 06:57:21 -05:00
/* Copy the user ACL rules from the source user 'src' to the destination
* user ' dst ' so that at the end of the process they ' ll have exactly the
* same rules ( but the names will continue to be the original ones ) . */
void ACLCopyUser ( user * dst , user * src ) {
listRelease ( dst - > passwords ) ;
listRelease ( dst - > patterns ) ;
dst - > passwords = listDup ( src - > passwords ) ;
dst - > patterns = listDup ( src - > patterns ) ;
memcpy ( dst - > allowed_commands , src - > allowed_commands ,
sizeof ( dst - > allowed_commands ) ) ;
dst - > flags = src - > flags ;
ACLResetSubcommands ( dst ) ;
/* Copy the allowed subcommands array of array of SDS strings. */
if ( src - > allowed_subcommands ) {
for ( int j = 0 ; j < USER_COMMAND_BITS_COUNT ; j + + ) {
if ( src - > allowed_subcommands [ j ] ) {
for ( int i = 0 ; src - > allowed_subcommands [ j ] [ i ] ; i + + )
{
ACLAddAllowedSubcommand ( dst , j ,
src - > allowed_subcommands [ j ] [ i ] ) ;
}
}
}
}
}
/* Free all the users registered in the radix tree 'users' and free the
* radix tree itself . */
void ACLFreeUsersSet ( rax * users ) {
2019-02-07 10:20:42 -05:00
raxFreeWithCallback ( users , ( void ( * ) ( void * ) ) ACLFreeUser ) ;
2019-02-07 06:57:21 -05:00
}
2019-01-18 12:16:03 -05:00
/* Given a command ID, this function set by reference 'word' and 'bit'
* so that user - > allowed_commands [ word ] will address the right word
* where the corresponding bit for the provided ID is stored , and
* so that user - > allowed_commands [ word ] & bit will identify that specific
* bit . The function returns C_ERR in case the specified ID overflows
* the bitmap in the user representation . */
2019-01-26 06:51:43 -05:00
int ACLGetCommandBitCoordinates ( uint64_t id , uint64_t * word , uint64_t * bit ) {
2019-01-23 02:10:57 -05:00
if ( id > = USER_COMMAND_BITS_COUNT ) return C_ERR ;
2019-01-18 12:16:03 -05:00
* word = id / sizeof ( uint64_t ) / 8 ;
2019-01-26 06:51:43 -05:00
* bit = 1ULL < < ( id % ( sizeof ( uint64_t ) * 8 ) ) ;
2019-01-18 12:16:03 -05:00
return C_OK ;
}
/* Check if the specified command bit is set for the specified user.
* The function returns 1 is the bit is set or 0 if it is not .
* Note that this function does not check the ALLCOMMANDS flag of the user
* but just the lowlevel bitmask .
*
* If the bit overflows the user internal represetation , zero is returned
* in order to disallow the execution of the command in such edge case . */
int ACLGetUserCommandBit ( user * u , unsigned long id ) {
uint64_t word , bit ;
if ( ACLGetCommandBitCoordinates ( id , & word , & bit ) = = C_ERR ) return 0 ;
2019-01-26 06:51:43 -05:00
return ( u - > allowed_commands [ word ] & bit ) ! = 0 ;
2019-01-18 12:16:03 -05:00
}
2019-01-23 02:14:56 -05:00
/* When +@all or allcommands is given, we set a reserved bit as well that we
* can later test , to see if the user has the right to execute " future commands " ,
* that is , commands loaded later via modules . */
int ACLUserCanExecuteFutureCommands ( user * u ) {
return ACLGetUserCommandBit ( u , USER_COMMAND_BITS_COUNT - 1 ) ;
}
2019-01-18 12:16:03 -05:00
/* Set the specified command bit for the specified user to 'value' (0 or 1).
* If the bit overflows the user internal represetation , no operation
2019-01-25 07:27:33 -05:00
* is performed . As a side effect of calling this function with a value of
* zero , the user flag ALLCOMMANDS is cleared since it is no longer possible
* to skip the command bit explicit test . */
2019-01-18 12:16:03 -05:00
void ACLSetUserCommandBit ( user * u , unsigned long id , int value ) {
uint64_t word , bit ;
2019-01-25 07:27:33 -05:00
if ( value = = 0 ) u - > flags & = ~ USER_FLAG_ALLCOMMANDS ;
2019-01-18 12:16:03 -05:00
if ( ACLGetCommandBitCoordinates ( id , & word , & bit ) = = C_ERR ) return ;
if ( value )
u - > allowed_commands [ word ] | = bit ;
else
u - > allowed_commands [ word ] & = ~ bit ;
}
2019-01-24 12:11:09 -05:00
/* This is like ACLSetUserCommandBit(), but instead of setting the specified
* ID , it will check all the commands in the category specified as argument ,
* and will set all the bits corresponding to such commands to the specified
* value . Since the category passed by the user may be non existing , the
* function returns C_ERR if the category was not found , or C_OK if it was
* found and the operation was performed . */
int ACLSetUserCommandBitsForCategory ( user * u , const char * category , int value ) {
uint64_t cflag = ACLGetCommandCategoryFlagByName ( category ) ;
if ( ! cflag ) return C_ERR ;
dictIterator * di = dictGetIterator ( server . orig_commands ) ;
dictEntry * de ;
while ( ( de = dictNext ( di ) ) ! = NULL ) {
struct redisCommand * cmd = dictGetVal ( de ) ;
2019-01-30 02:09:05 -05:00
if ( cmd - > flags & cflag ) {
ACLSetUserCommandBit ( u , cmd - > id , value ) ;
ACLResetSubcommandsForCommand ( u , cmd - > id ) ;
}
2019-01-24 12:11:09 -05:00
}
dictReleaseIterator ( di ) ;
return C_OK ;
}
2019-01-29 11:25:02 -05:00
/* Return the number of commands allowed (on) and denied (off) for the user 'u'
* in the subset of commands flagged with the specified category name .
* If the categoty name is not valid , C_ERR is returend , otherwise C_OK is
* returned and on and off are populated by reference . */
int ACLCountCategoryBitsForUser ( user * u , unsigned long * on , unsigned long * off ,
const char * category )
{
uint64_t cflag = ACLGetCommandCategoryFlagByName ( category ) ;
if ( ! cflag ) return C_ERR ;
* on = * off = 0 ;
dictIterator * di = dictGetIterator ( server . orig_commands ) ;
dictEntry * de ;
while ( ( de = dictNext ( di ) ) ! = NULL ) {
struct redisCommand * cmd = dictGetVal ( de ) ;
if ( cmd - > flags & cflag ) {
if ( ACLGetUserCommandBit ( u , cmd - > id ) )
( * on ) + + ;
else
( * off ) + + ;
}
}
dictReleaseIterator ( di ) ;
return C_OK ;
}
/* This function returns an SDS string representing the specified user ACL
* rules related to command execution , in the same format you could set them
* back using ACL SETUSER . The function will return just the set of rules needed
* to recreate the user commands bitmap , without including other user flags such
* as on / off , passwords and so forth . The returned string always starts with
* the + @ all or - @ all rule , depending on the user bitmap , and is followed , if
* needed , by the other rules needed to narrow or extend what the user can do . */
sds ACLDescribeUserCommandRules ( user * u ) {
sds rules = sdsempty ( ) ;
int additive ; /* If true we start from -@all and add, otherwise if
false we start from + @ all and remove . */
/* This code is based on a trick: as we generate the rules, we apply
* them to a fake user , so that as we go we still know what are the
* bit differences we should try to address by emitting more rules . */
user fu = { 0 } ;
user * fakeuser = & fu ;
/* Here we want to understand if we should start with +@all and remove
* the commands corresponding to the bits that are not set in the user
* commands bitmap , or the contrary . Note that semantically the two are
* different . For instance starting with + @ all and subtracting , the user
* will be able to execute future commands , while - @ all and adding will just
* allow the user the run the selected commands and / or categories .
* How do we test for that ? We use the trick of a reserved command ID bit
* that is set only by + @ all ( and its alias " allcommands " ) . */
if ( ACLUserCanExecuteFutureCommands ( u ) ) {
additive = 0 ;
rules = sdscat ( rules , " +@all " ) ;
2019-01-29 12:54:21 -05:00
ACLSetUser ( fakeuser , " +@all " , - 1 ) ;
2019-01-29 11:25:02 -05:00
} else {
additive = 1 ;
rules = sdscat ( rules , " -@all " ) ;
2019-01-29 12:54:21 -05:00
ACLSetUser ( fakeuser , " -@all " , - 1 ) ;
2019-01-29 11:25:02 -05:00
}
/* Try to add or subtract each category one after the other. Often a
* single category will not perfectly match the set of commands into
* it , so at the end we do a final pass adding / removing the single commands
* needed to make the bitmap exactly match . */
for ( int j = 0 ; ACLCommandCategories [ j ] . flag ! = 0 ; j + + ) {
unsigned long on , off ;
ACLCountCategoryBitsForUser ( u , & on , & off , ACLCommandCategories [ j ] . name ) ;
if ( ( additive & & on > off ) | | ( ! additive & & off > on ) ) {
2019-01-29 12:54:21 -05:00
sds op = sdsnewlen ( additive ? " +@ " : " -@ " , 2 ) ;
op = sdscat ( op , ACLCommandCategories [ j ] . name ) ;
ACLSetUser ( fakeuser , op , - 1 ) ;
rules = sdscatsds ( rules , op ) ;
2019-01-29 11:25:02 -05:00
rules = sdscatlen ( rules , " " , 1 ) ;
2019-01-29 12:54:21 -05:00
sdsfree ( op ) ;
2019-01-29 11:25:02 -05:00
}
}
/* Fix the final ACLs with single commands differences. */
2019-01-29 12:41:11 -05:00
dictIterator * di = dictGetIterator ( server . orig_commands ) ;
dictEntry * de ;
while ( ( de = dictNext ( di ) ) ! = NULL ) {
struct redisCommand * cmd = dictGetVal ( de ) ;
int userbit = ACLGetUserCommandBit ( u , cmd - > id ) ;
2019-01-29 12:54:21 -05:00
int fakebit = ACLGetUserCommandBit ( fakeuser , cmd - > id ) ;
2019-01-29 12:41:11 -05:00
if ( userbit ! = fakebit ) {
rules = sdscatlen ( rules , userbit ? " + " : " - " , 1 ) ;
rules = sdscat ( rules , cmd - > name ) ;
rules = sdscatlen ( rules , " " , 1 ) ;
2019-01-29 12:54:21 -05:00
ACLSetUserCommandBit ( fakeuser , cmd - > id , userbit ) ;
2019-01-29 12:41:11 -05:00
}
2019-01-30 02:17:33 -05:00
/* Emit the subcommands if there are any. */
if ( userbit = = 0 & & u - > allowed_subcommands & &
u - > allowed_subcommands [ cmd - > id ] )
{
for ( int j = 0 ; u - > allowed_subcommands [ cmd - > id ] [ j ] ; j + + ) {
rules = sdscatlen ( rules , " + " , 1 ) ;
rules = sdscat ( rules , cmd - > name ) ;
rules = sdscatlen ( rules , " | " , 1 ) ;
rules = sdscatsds ( rules , u - > allowed_subcommands [ cmd - > id ] [ j ] ) ;
rules = sdscatlen ( rules , " " , 1 ) ;
}
}
2019-01-29 12:41:11 -05:00
}
dictReleaseIterator ( di ) ;
2019-01-29 11:25:02 -05:00
/* Trim the final useless space. */
2019-01-29 12:41:11 -05:00
sdsrange ( rules , 0 , - 2 ) ;
2019-01-29 11:25:02 -05:00
/* This is technically not needed, but we want to verify that now the
* predicted bitmap is exactly the same as the user bitmap , and abort
* otherwise , because aborting is better than a security risk in this
* code path . */
2019-01-29 12:54:21 -05:00
if ( memcmp ( fakeuser - > allowed_commands ,
2019-01-29 11:25:02 -05:00
u - > allowed_commands ,
2019-01-29 12:54:21 -05:00
sizeof ( u - > allowed_commands ) ) ! = 0 )
{
serverLog ( LL_WARNING ,
" CRITICAL ERROR: User ACLs don't match final bitmap: '%s' " ,
rules ) ;
serverPanic ( " No bitmap match in ACLDescribeUserCommandRules() " ) ;
}
2019-01-29 11:25:02 -05:00
return rules ;
}
2019-01-31 10:49:22 -05:00
/* This is similar to ACLDescribeUserCommandRules(), however instead of
* describing just the user command rules , everything is described : user
* flags , keys , passwords and finally the command rules obtained via
* the ACLDescribeUserCommandRules ( ) function . This is the function we call
* when we want to rewrite the configuration files describing ACLs and
* in order to show users with ACL LIST . */
sds ACLDescribeUser ( user * u ) {
sds res = sdsempty ( ) ;
/* Flags. */
for ( int j = 0 ; ACLUserFlags [ j ] . flag ; j + + ) {
2019-01-31 11:04:42 -05:00
/* Skip the allcommands and allkeys flags because they'll be emitted
* later as ~ * and + @ all . */
if ( ACLUserFlags [ j ] . flag = = USER_FLAG_ALLKEYS | |
ACLUserFlags [ j ] . flag = = USER_FLAG_ALLCOMMANDS ) continue ;
2019-01-31 10:49:22 -05:00
if ( u - > flags & ACLUserFlags [ j ] . flag ) {
res = sdscat ( res , ACLUserFlags [ j ] . name ) ;
res = sdscatlen ( res , " " , 1 ) ;
}
}
/* Passwords. */
listIter li ;
listNode * ln ;
listRewind ( u - > passwords , & li ) ;
while ( ( ln = listNext ( & li ) ) ) {
sds thispass = listNodeValue ( ln ) ;
res = sdscatlen ( res , " > " , 1 ) ;
res = sdscatsds ( res , thispass ) ;
res = sdscatlen ( res , " " , 1 ) ;
}
/* Key patterns. */
if ( u - > flags & USER_FLAG_ALLKEYS ) {
res = sdscatlen ( res , " ~* " , 3 ) ;
} else {
listRewind ( u - > patterns , & li ) ;
while ( ( ln = listNext ( & li ) ) ) {
sds thispat = listNodeValue ( ln ) ;
res = sdscatlen ( res , " ~ " , 1 ) ;
res = sdscatsds ( res , thispat ) ;
res = sdscatlen ( res , " " , 1 ) ;
}
}
/* Command rules. */
sds rules = ACLDescribeUserCommandRules ( u ) ;
res = sdscatsds ( res , rules ) ;
sdsfree ( rules ) ;
return res ;
}
2019-01-18 12:16:03 -05:00
/* Get a command from the original command table, that is not affected
* by the command renaming operations : we base all the ACL work from that
* table , so that ACLs are valid regardless of command renaming . */
struct redisCommand * ACLLookupCommand ( const char * name ) {
struct redisCommand * cmd ;
sds sdsname = sdsnew ( name ) ;
cmd = dictFetchValue ( server . orig_commands , sdsname ) ;
sdsfree ( sdsname ) ;
return cmd ;
}
2019-01-21 11:08:54 -05:00
/* Flush the array of allowed subcommands for the specified user
* and command ID . */
void ACLResetSubcommandsForCommand ( user * u , unsigned long id ) {
if ( u - > allowed_subcommands & & u - > allowed_subcommands [ id ] ) {
zfree ( u - > allowed_subcommands [ id ] ) ;
u - > allowed_subcommands [ id ] = NULL ;
}
}
2019-01-28 12:27:41 -05:00
/* Flush the entire table of subcommands. This is useful on +@all, -@all
* or similar to return back to the minimal memory usage ( and checks to do )
* for the user . */
void ACLResetSubcommands ( user * u ) {
if ( u - > allowed_subcommands = = NULL ) return ;
2019-01-30 05:50:30 -05:00
for ( int j = 0 ; j < USER_COMMAND_BITS_COUNT ; j + + ) {
if ( u - > allowed_subcommands [ j ] ) {
for ( int i = 0 ; u - > allowed_subcommands [ j ] [ i ] ; i + + )
sdsfree ( u - > allowed_subcommands [ j ] [ i ] ) ;
zfree ( u - > allowed_subcommands [ j ] ) ;
}
}
2019-01-28 12:27:41 -05:00
zfree ( u - > allowed_subcommands ) ;
u - > allowed_subcommands = NULL ;
}
2019-01-28 12:40:54 -05:00
/* Add a subcommand to the list of subcommands for the user 'u' and
* the command id specified . */
void ACLAddAllowedSubcommand ( user * u , unsigned long id , const char * sub ) {
/* If this is the first subcommand to be configured for
* this user , we have to allocate the subcommands array . */
if ( u - > allowed_subcommands = = NULL ) {
u - > allowed_subcommands = zcalloc ( USER_COMMAND_BITS_COUNT *
sizeof ( sds * ) ) ;
}
/* We also need to enlarge the allocation pointing to the
* null terminated SDS array , to make space for this one .
* To start check the current size , and while we are here
* make sure the subcommand is not already specified inside . */
long items = 0 ;
if ( u - > allowed_subcommands [ id ] ) {
while ( u - > allowed_subcommands [ id ] [ items ] ) {
/* If it's already here do not add it again. */
if ( ! strcasecmp ( u - > allowed_subcommands [ id ] [ items ] , sub ) ) return ;
items + + ;
}
}
/* Now we can make space for the new item (and the null term). */
items + = 2 ;
u - > allowed_subcommands [ id ] = zrealloc ( u - > allowed_subcommands [ id ] ,
sizeof ( sds ) * items ) ;
u - > allowed_subcommands [ id ] [ items - 2 ] = sdsnew ( sub ) ;
u - > allowed_subcommands [ id ] [ items - 1 ] = NULL ;
}
2019-01-11 05:25:55 -05:00
/* Set user properties according to the string "op". The following
* is a description of what different strings will do :
*
2019-01-15 12:16:20 -05:00
* on Enable the user : it is possible to authenticate as this user .
* off Disable the user : it ' s no longer possible to authenticate
* with this user , however the already authenticated connections
* will still work .
2019-01-11 05:25:55 -05:00
* + < command > Allow the execution of that command
* - < command > Disallow the execution of that command
* + @ < category > Allow the execution of all the commands in such category
2019-01-23 06:15:10 -05:00
* with valid categories are like @ admin , @ set , @ sortedset , . . .
* and so forth , see the full list in the server . c file where
* the Redis command table is described and defined .
2019-01-23 05:05:54 -05:00
* The special category @ all means all the commands , but currently
* present in the server , and that will be loaded in the future
* via modules .
2019-01-11 05:25:55 -05:00
* + < command > | subcommand Allow a specific subcommand of an otherwise
* disabled command . Note that this form is not
* allowed as negative like - DEBUG | SEGFAULT , but
* only additive starting with " + " .
2019-01-23 05:05:54 -05:00
* allcommands Alias for + @ all . Note that it implies the ability to execute
* all the future commands loaded via the modules system .
* nocommands Alias for - @ all .
2019-01-11 07:03:50 -05:00
* ~ < pattern > Add a pattern of keys that can be mentioned as part of
2019-01-11 05:25:55 -05:00
* commands . For instance ~ * allows all the keys . The pattern
* is a glob - style pattern like the one of KEYS .
2019-01-11 07:03:50 -05:00
* It is possible to specify multiple patterns .
2019-01-21 12:23:28 -05:00
* allkeys Alias for ~ *
* resetkeys Flush the list of allowed keys patterns .
2019-01-11 05:25:55 -05:00
* > < password > Add this passowrd to the list of valid password for the user .
* For example > mypass will add " mypass " to the list .
2019-01-15 12:16:20 -05:00
* This directive clears the " nopass " flag ( see later ) .
2019-01-11 05:25:55 -05:00
* < < password > Remove this password from the list of valid passwords .
2019-01-15 07:16:31 -05:00
* nopass All the set passwords of the user are removed , and the user
* is flagged as requiring no password : it means that every
* password will work against this user . If this directive is
* used for the default user , every new connection will be
* immediately authenticated with the default user without
* any explicit AUTH command required . Note that the " resetpass "
* directive will clear this condition .
* resetpass Flush the list of allowed passwords . Moreover removes the
* " nopass " status . After " resetpass " the user has no associated
* passwords and there is no way to authenticate without adding
* some password ( or setting it as " nopass " later ) .
2019-01-11 07:03:50 -05:00
* reset Performs the following actions : resetpass , resetkeys , off ,
* - @ all . The user returns to the same state it has immediately
* after its creation .
*
2019-01-15 06:58:54 -05:00
* The ' op ' string must be null terminated . The ' oplen ' argument should
* specify the length of the ' op ' string in case the caller requires to pass
* binary data ( for instance the > password form may use a binary password ) .
* Otherwise the field can be set to - 1 and the function will use strlen ( )
* to determine the length .
*
2019-01-11 07:03:50 -05:00
* The function returns C_OK if the action to perform was understood because
* the ' op ' string made sense . Otherwise C_ERR is returned if the operation
* is unknown or has some syntax error .
2019-01-18 12:16:03 -05:00
*
* When an error is returned , errno is set to the following values :
*
* EINVAL : The specified opcode is not understood .
2019-01-24 12:15:46 -05:00
* ENOENT : The command name or command category provided with + or - is not
2019-01-30 02:25:08 -05:00
* known .
* EBUSY : The subcommand you want to add is about a command that is currently
* fully added .
2019-01-30 09:59:45 -05:00
* EEXIST : You are adding a key pattern after " * " was already added . This is
* almost surely an error on the user side .
2019-01-30 02:25:08 -05:00
*/
2019-01-15 06:58:54 -05:00
int ACLSetUser ( user * u , const char * op , ssize_t oplen ) {
if ( oplen = = - 1 ) oplen = strlen ( op ) ;
2019-01-11 07:03:50 -05:00
if ( ! strcasecmp ( op , " on " ) ) {
u - > flags | = USER_FLAG_ENABLED ;
2019-01-31 10:49:22 -05:00
u - > flags & = ~ USER_FLAG_DISABLED ;
2019-01-11 07:03:50 -05:00
} else if ( ! strcasecmp ( op , " off " ) ) {
2019-01-31 10:49:22 -05:00
u - > flags | = USER_FLAG_DISABLED ;
2019-01-11 07:03:50 -05:00
u - > flags & = ~ USER_FLAG_ENABLED ;
} else if ( ! strcasecmp ( op , " allkeys " ) | |
! strcasecmp ( op , " ~* " ) )
{
u - > flags | = USER_FLAG_ALLKEYS ;
2019-01-21 12:18:39 -05:00
listEmpty ( u - > patterns ) ;
} else if ( ! strcasecmp ( op , " resetkeys " ) ) {
u - > flags & = ~ USER_FLAG_ALLKEYS ;
listEmpty ( u - > patterns ) ;
2019-01-14 07:18:12 -05:00
} else if ( ! strcasecmp ( op , " allcommands " ) | |
! strcasecmp ( op , " +@all " ) )
{
2019-01-14 10:09:29 -05:00
memset ( u - > allowed_commands , 255 , sizeof ( u - > allowed_commands ) ) ;
2019-01-14 07:18:12 -05:00
u - > flags | = USER_FLAG_ALLCOMMANDS ;
2019-01-28 12:27:41 -05:00
ACLResetSubcommands ( u ) ;
2019-01-21 12:23:28 -05:00
} else if ( ! strcasecmp ( op , " nocommands " ) | |
! strcasecmp ( op , " -@all " ) )
{
memset ( u - > allowed_commands , 0 , sizeof ( u - > allowed_commands ) ) ;
u - > flags & = ~ USER_FLAG_ALLCOMMANDS ;
2019-01-28 12:27:41 -05:00
ACLResetSubcommands ( u ) ;
2019-01-15 07:16:31 -05:00
} else if ( ! strcasecmp ( op , " nopass " ) ) {
u - > flags | = USER_FLAG_NOPASS ;
listEmpty ( u - > passwords ) ;
2019-01-18 05:26:29 -05:00
} else if ( ! strcasecmp ( op , " resetpass " ) ) {
u - > flags & = ~ USER_FLAG_NOPASS ;
listEmpty ( u - > passwords ) ;
2019-01-15 06:58:54 -05:00
} else if ( op [ 0 ] = = ' > ' ) {
sds newpass = sdsnewlen ( op + 1 , oplen - 1 ) ;
listNode * ln = listSearchKey ( u - > passwords , newpass ) ;
/* Avoid re-adding the same password multiple times. */
if ( ln = = NULL ) listAddNodeTail ( u - > passwords , newpass ) ;
2019-01-15 12:16:20 -05:00
u - > flags & = ~ USER_FLAG_NOPASS ;
2019-01-15 06:58:54 -05:00
} else if ( op [ 0 ] = = ' < ' ) {
sds delpass = sdsnewlen ( op + 1 , oplen - 1 ) ;
listNode * ln = listSearchKey ( u - > passwords , delpass ) ;
if ( ln ) listDelNode ( u - > passwords , ln ) ;
sdsfree ( delpass ) ;
2019-01-16 07:29:04 -05:00
} else if ( op [ 0 ] = = ' ~ ' ) {
2019-01-30 09:59:45 -05:00
if ( u - > flags & USER_FLAG_ALLKEYS ) {
errno = EEXIST ;
return C_ERR ;
}
2019-01-16 07:29:04 -05:00
sds newpat = sdsnewlen ( op + 1 , oplen - 1 ) ;
listNode * ln = listSearchKey ( u - > patterns , newpat ) ;
/* Avoid re-adding the same pattern multiple times. */
if ( ln = = NULL ) listAddNodeTail ( u - > patterns , newpat ) ;
u - > flags & = ~ USER_FLAG_ALLKEYS ;
2019-01-18 12:16:03 -05:00
} else if ( op [ 0 ] = = ' + ' & & op [ 1 ] ! = ' @ ' ) {
2019-01-21 11:08:54 -05:00
if ( strchr ( op , ' | ' ) = = NULL ) {
if ( ACLLookupCommand ( op + 1 ) = = NULL ) {
errno = ENOENT ;
return C_ERR ;
}
unsigned long id = ACLGetCommandID ( op + 1 ) ;
ACLSetUserCommandBit ( u , id , 1 ) ;
ACLResetSubcommandsForCommand ( u , id ) ;
} else {
/* Split the command and subcommand parts. */
char * copy = zstrdup ( op + 1 ) ;
char * sub = strchr ( copy , ' | ' ) ;
sub [ 0 ] = ' \0 ' ;
sub + + ;
/* Check if the command exists. We can't check the
* subcommand to see if it is valid . */
if ( ACLLookupCommand ( copy ) = = NULL ) {
2019-01-30 02:20:31 -05:00
zfree ( copy ) ;
2019-01-21 11:08:54 -05:00
errno = ENOENT ;
return C_ERR ;
}
unsigned long id = ACLGetCommandID ( copy ) ;
/* The subcommand cannot be empty, so things like DEBUG|
* are syntax errors of course . */
if ( strlen ( sub ) = = 0 ) {
zfree ( copy ) ;
errno = EINVAL ;
return C_ERR ;
}
2019-01-30 02:25:08 -05:00
/* The command should not be set right now in the command
* bitmap , because adding a subcommand of a fully added
* command is probably an error on the user side . */
if ( ACLGetUserCommandBit ( u , id ) = = 1 ) {
zfree ( copy ) ;
errno = EBUSY ;
return C_ERR ;
}
2019-01-28 12:40:54 -05:00
/* Add the subcommand to the list of valid ones. */
ACLAddAllowedSubcommand ( u , id , sub ) ;
2019-01-21 11:08:54 -05:00
/* We have to clear the command bit so that we force the
* subcommand check . */
ACLSetUserCommandBit ( u , id , 0 ) ;
zfree ( copy ) ;
2019-01-18 12:16:03 -05:00
}
} else if ( op [ 0 ] = = ' - ' & & op [ 1 ] ! = ' @ ' ) {
if ( ACLLookupCommand ( op + 1 ) = = NULL ) {
errno = ENOENT ;
return C_ERR ;
}
unsigned long id = ACLGetCommandID ( op + 1 ) ;
ACLSetUserCommandBit ( u , id , 0 ) ;
2019-01-21 11:08:54 -05:00
ACLResetSubcommandsForCommand ( u , id ) ;
2019-01-24 12:15:46 -05:00
} else if ( ( op [ 0 ] = = ' + ' | | op [ 0 ] = = ' - ' ) & & op [ 1 ] = = ' @ ' ) {
int bitval = op [ 0 ] = = ' + ' ? 1 : 0 ;
if ( ACLSetUserCommandBitsForCategory ( u , op + 2 , bitval ) = = C_ERR ) {
errno = ENOENT ;
return C_ERR ;
}
2019-01-21 12:21:02 -05:00
} else if ( ! strcasecmp ( op , " reset " ) ) {
serverAssert ( ACLSetUser ( u , " resetpass " , - 1 ) = = C_OK ) ;
serverAssert ( ACLSetUser ( u , " resetkeys " , - 1 ) = = C_OK ) ;
serverAssert ( ACLSetUser ( u , " off " , - 1 ) = = C_OK ) ;
serverAssert ( ACLSetUser ( u , " -@all " , - 1 ) = = C_OK ) ;
2019-01-11 07:03:50 -05:00
} else {
2019-01-18 12:16:03 -05:00
errno = EINVAL ;
2019-01-11 07:03:50 -05:00
return C_ERR ;
}
return C_OK ;
2019-01-11 05:25:55 -05:00
}
2019-01-30 10:02:25 -05:00
/* Return a description of the error that occurred in ACLSetUser() according to
* the errno value set by the function on error . */
char * ACLSetUserStringError ( void ) {
char * errmsg = " Wrong format " ;
if ( errno = = ENOENT )
errmsg = " Unknown command or category name in ACL " ;
else if ( errno = = EINVAL )
errmsg = " Syntax error " ;
else if ( errno = = EBUSY )
errmsg = " Adding a subcommand of a command already fully "
" added is not allowed. Remove the command to start. "
" Example: -DEBUG +DEBUG|DIGEST " ;
else if ( errno = = EEXIST )
errmsg = " Adding a pattern after the * pattern (or the "
" 'allkeys' flag) is not valid and does not have any "
" effect. Try 'resetkeys' to start with an empty "
" list of patterns " ;
return errmsg ;
}
2019-01-18 05:49:30 -05:00
/* Return the first password of the default user or NULL.
* This function is needed for backward compatibility with the old
* directive " requirepass " when Redis supported a single global
* password . */
sds ACLDefaultUserFirstPassword ( void ) {
if ( listLength ( DefaultUser - > passwords ) = = 0 ) return NULL ;
listNode * first = listFirst ( DefaultUser - > passwords ) ;
return listNodeValue ( first ) ;
}
2019-02-07 06:20:30 -05:00
/* Initialize the default user, that will always exist for all the process
* lifetime . */
void ACLInitDefaultUser ( void ) {
2019-01-11 05:02:55 -05:00
DefaultUser = ACLCreateUser ( " default " , 7 ) ;
2019-01-15 06:58:54 -05:00
ACLSetUser ( DefaultUser , " +@all " , - 1 ) ;
2019-01-16 12:31:05 -05:00
ACLSetUser ( DefaultUser , " ~* " , - 1 ) ;
2019-01-15 06:58:54 -05:00
ACLSetUser ( DefaultUser , " on " , - 1 ) ;
2019-01-15 07:16:31 -05:00
ACLSetUser ( DefaultUser , " nopass " , - 1 ) ;
2019-01-10 10:39:32 -05:00
}
2019-02-07 06:20:30 -05:00
/* Initialization of the ACL subsystem. */
void ACLInit ( void ) {
Users = raxNew ( ) ;
UsersToLoad = listCreate ( ) ;
ACLInitDefaultUser ( ) ;
}
2018-12-21 11:16:22 -05:00
/* Check the username and password pair and return C_OK if they are valid,
* otherwise C_ERR is returned and errno is set to :
*
* EINVAL : if the username - password do not match .
* ENONENT : if the specified user does not exist at all .
*/
int ACLCheckUserCredentials ( robj * username , robj * password ) {
2019-01-15 12:16:20 -05:00
user * u = ACLGetUserByName ( username - > ptr , sdslen ( username - > ptr ) ) ;
if ( u = = NULL ) {
2018-12-21 11:16:22 -05:00
errno = ENOENT ;
return C_ERR ;
}
2019-01-15 12:16:20 -05:00
/* Disabled users can't login. */
2019-01-31 10:49:22 -05:00
if ( u - > flags & USER_FLAG_DISABLED ) {
2018-12-21 11:16:22 -05:00
errno = EINVAL ;
return C_ERR ;
}
2019-01-15 12:16:20 -05:00
/* If the user is configured to don't require any password, we
* are already fine here . */
if ( u - > flags & USER_FLAG_NOPASS ) return C_OK ;
/* Check all the user passwords for at least one to match. */
listIter li ;
listNode * ln ;
listRewind ( u - > passwords , & li ) ;
while ( ( ln = listNext ( & li ) ) ) {
sds thispass = listNodeValue ( ln ) ;
if ( ! time_independent_strcmp ( password - > ptr , thispass ) )
return C_OK ;
}
/* If we reached this point, no password matched. */
errno = EINVAL ;
return C_ERR ;
2018-12-21 11:16:22 -05:00
}
2019-01-09 15:31:29 -05:00
/* For ACL purposes, every user has a bitmap with the commands that such
* user is allowed to execute . In order to populate the bitmap , every command
* should have an assigned ID ( that is used to index the bitmap ) . This function
* creates such an ID : it uses sequential IDs , reusing the same ID for the same
* command name , so that a command retains the same ID in case of modules that
* are unloaded and later reloaded . */
unsigned long ACLGetCommandID ( const char * cmdname ) {
static rax * map = NULL ;
2019-01-09 15:47:43 -05:00
static unsigned long nextid = 0 ;
2019-01-09 15:31:29 -05:00
2019-01-18 07:24:53 -05:00
sds lowername = sdsnew ( cmdname ) ;
sdstolower ( lowername ) ;
2019-01-09 15:31:29 -05:00
if ( map = = NULL ) map = raxNew ( ) ;
2019-01-18 07:24:53 -05:00
void * id = raxFind ( map , ( unsigned char * ) lowername , sdslen ( lowername ) ) ;
if ( id ! = raxNotFound ) {
sdsfree ( lowername ) ;
return ( unsigned long ) id ;
}
raxInsert ( map , ( unsigned char * ) lowername , strlen ( lowername ) ,
( void * ) nextid , NULL ) ;
sdsfree ( lowername ) ;
2019-01-25 07:07:20 -05:00
unsigned long thisid = nextid ;
2019-01-23 02:10:57 -05:00
nextid + + ;
/* We never assign the last bit in the user commands bitmap structure,
* this way we can later check if this bit is set , understanding if the
* current ACL for the user was created starting with a + @ all to add all
* the possible commands and just subtracting other single commands or
* categories , or if , instead , the ACL was created just adding commands
* and command categories from scratch , not allowing future commands by
* default ( loaded via modules ) . This is useful when rewriting the ACLs
* with ACL SAVE . */
if ( nextid = = USER_COMMAND_BITS_COUNT - 1 ) nextid + + ;
2019-01-25 07:07:20 -05:00
return thisid ;
2019-01-09 15:31:29 -05:00
}
2019-01-10 10:33:48 -05:00
/* Return an username by its name, or NULL if the user does not exist. */
user * ACLGetUserByName ( const char * name , size_t namelen ) {
2019-01-10 10:40:45 -05:00
void * myuser = raxFind ( Users , ( unsigned char * ) name , namelen ) ;
if ( myuser = = raxNotFound ) return NULL ;
return myuser ;
2019-01-10 10:33:48 -05:00
}
2019-01-10 10:35:55 -05:00
2019-01-14 07:19:50 -05:00
/* Check if the command ready to be excuted in the client 'c', and already
* referenced by c - > cmd , can be executed by this client according to the
* ACls associated to the client user c - > user .
*
2019-01-16 12:31:05 -05:00
* If the user can execute the command ACL_OK is returned , otherwise
* ACL_DENIED_CMD or ACL_DENIED_KEY is returned : the first in case the
* command cannot be executed because the user is not allowed to run such
* command , the second if the command is denied because the user is trying
* to access keys that are not among the specified patterns . */
2019-01-14 07:19:50 -05:00
int ACLCheckCommandPerm ( client * c ) {
2019-01-14 12:35:21 -05:00
user * u = c - > user ;
uint64_t id = c - > cmd - > id ;
2019-01-14 07:19:50 -05:00
/* If there is no associated user, the connection can run anything. */
2019-01-16 12:31:05 -05:00
if ( u = = NULL ) return ACL_OK ;
2019-01-14 12:35:21 -05:00
2019-01-14 07:19:50 -05:00
/* Check if the user can execute this command. */
2019-01-15 12:26:44 -05:00
if ( ! ( u - > flags & USER_FLAG_ALLCOMMANDS ) & &
c - > cmd - > proc ! = authCommand )
{
2019-01-14 12:35:21 -05:00
/* If the bit is not set we have to check further, in case the
* command is allowed just with that specific subcommand . */
2019-01-18 07:39:28 -05:00
if ( ACLGetUserCommandBit ( u , id ) = = 0 ) {
2019-01-14 12:35:21 -05:00
/* Check if the subcommand matches. */
2019-01-28 12:15:59 -05:00
if ( c - > argc < 2 | |
u - > allowed_subcommands = = NULL | |
u - > allowed_subcommands [ id ] = = NULL )
{
2019-01-16 12:31:05 -05:00
return ACL_DENIED_CMD ;
2019-01-28 12:15:59 -05:00
}
2019-01-14 12:35:21 -05:00
long subid = 0 ;
while ( 1 ) {
2019-01-16 12:31:05 -05:00
if ( u - > allowed_subcommands [ id ] [ subid ] = = NULL )
return ACL_DENIED_CMD ;
2019-01-14 12:35:21 -05:00
if ( ! strcasecmp ( c - > argv [ 1 ] - > ptr ,
u - > allowed_subcommands [ id ] [ subid ] ) )
break ; /* Subcommand match found. Stop here. */
subid + + ;
}
}
2019-01-14 07:19:50 -05:00
}
2019-01-14 12:35:21 -05:00
/* Check if the user can execute commands explicitly touching the keys
* mentioned in the command arguments . */
if ( ! ( c - > user - > flags & USER_FLAG_ALLKEYS ) & &
( c - > cmd - > getkeys_proc | | c - > cmd - > firstkey ) )
{
2019-01-16 07:39:04 -05:00
int numkeys ;
int * keyidx = getKeysFromCommand ( c - > cmd , c - > argv , c - > argc , & numkeys ) ;
for ( int j = 0 ; j < numkeys ; j + + ) {
listIter li ;
listNode * ln ;
2019-01-25 06:06:18 -05:00
listRewind ( u - > patterns , & li ) ;
2019-01-16 07:39:04 -05:00
/* Test this key against every pattern. */
2019-01-16 07:50:00 -05:00
int match = 0 ;
2019-01-16 07:39:04 -05:00
while ( ( ln = listNext ( & li ) ) ) {
sds pattern = listNodeValue ( ln ) ;
size_t plen = sdslen ( pattern ) ;
int idx = keyidx [ j ] ;
if ( stringmatchlen ( pattern , plen , c - > argv [ idx ] - > ptr ,
sdslen ( c - > argv [ idx ] - > ptr ) , 0 ) )
{
match = 1 ;
break ;
}
}
2019-01-25 06:35:18 -05:00
if ( ! match ) {
getKeysFreeResult ( keyidx ) ;
return ACL_DENIED_KEY ;
}
2019-01-16 07:39:04 -05:00
}
2019-01-25 07:00:49 -05:00
getKeysFreeResult ( keyidx ) ;
2019-01-14 07:19:50 -05:00
}
/* If we survived all the above checks, the user can execute the
* command . */
2019-01-16 12:31:05 -05:00
return ACL_OK ;
2019-01-14 07:19:50 -05:00
}
2019-02-01 06:20:09 -05:00
/* =============================================================================
* ACL loading / saving functions
* = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = */
/* Given an argument vector describing a user in the form:
*
* user < username > . . . ACL rules and flags . . .
*
* this function validates , and if the syntax is valid , appends
* the user definition to a list for later loading .
*
* The rules are tested for validity and if there obvious syntax errors
* the function returns C_ERR and does nothing , otherwise C_OK is returned
* and the user is appended to the list .
*
* Note that this function cannot stop in case of commands that are not found
* and , in that case , the error will be emitted later , because certain
2019-02-04 07:00:38 -05:00
* commands may be defined later once modules are loaded .
*
* When an error is detected and C_ERR is returned , the function populates
* by reference ( if not set to NULL ) the argc_err argument with the index
* of the argv vector that caused the error . */
int ACLAppendUserForLoading ( sds * argv , int argc , int * argc_err ) {
if ( argc < 2 | | strcasecmp ( argv [ 0 ] , " user " ) ) {
if ( argc_err ) * argc_err = 0 ;
return C_ERR ;
}
2019-02-01 07:02:41 -05:00
/* Try to apply the user rules in a fake user to see if they
* are actually valid . */
2019-02-06 10:19:17 -05:00
user * fakeuser = ACLCreateUnlinkedUser ( ) ;
2019-02-04 06:55:26 -05:00
2019-02-01 07:02:41 -05:00
for ( int j = 2 ; j < argc ; j + + ) {
if ( ACLSetUser ( fakeuser , argv [ j ] , sdslen ( argv [ j ] ) ) = = C_ERR ) {
2019-02-04 06:55:26 -05:00
if ( errno ! = ENOENT ) {
ACLFreeUser ( fakeuser ) ;
2019-02-04 07:00:38 -05:00
if ( argc_err ) * argc_err = j ;
2019-02-04 06:55:26 -05:00
return C_ERR ;
}
2019-02-01 07:02:41 -05:00
}
}
/* Rules look valid, let's append the user to the list. */
sds * copy = zmalloc ( sizeof ( sds ) * argc ) ;
for ( int j = 1 ; j < argc ; j + + ) copy [ j - 1 ] = sdsdup ( argv [ j ] ) ;
copy [ argc - 1 ] = NULL ;
listAddNodeTail ( UsersToLoad , copy ) ;
2019-02-04 06:55:26 -05:00
ACLFreeUser ( fakeuser ) ;
2019-02-01 07:02:41 -05:00
return C_OK ;
2019-02-01 06:20:09 -05:00
}
2019-02-04 10:35:15 -05:00
/* This function will load the configured users appended to the server
* configuration via ACLAppendUserForLoading ( ) . On loading errors it will
* log an error and return C_ERR , otherwise C_OK will be returned . */
int ACLLoadConfiguredUsers ( void ) {
listIter li ;
listNode * ln ;
listRewind ( UsersToLoad , & li ) ;
while ( ( ln = listNext ( & li ) ) ! = NULL ) {
sds * aclrules = listNodeValue ( ln ) ;
2019-02-05 04:52:05 -05:00
sds username = aclrules [ 0 ] ;
user * u = ACLCreateUser ( username , sdslen ( username ) ) ;
2019-02-04 10:35:15 -05:00
if ( ! u ) {
2019-02-05 04:52:05 -05:00
u = ACLGetUserByName ( username , sdslen ( username ) ) ;
serverAssert ( u ! = NULL ) ;
ACLSetUser ( u , " reset " , - 1 ) ;
2019-02-04 10:35:15 -05:00
}
/* Load every rule defined for this user. */
for ( int j = 1 ; aclrules [ j ] ; j + + ) {
if ( ACLSetUser ( u , aclrules [ j ] , sdslen ( aclrules [ j ] ) ) ! = C_OK ) {
char * errmsg = ACLSetUserStringError ( ) ;
serverLog ( LL_WARNING , " Error loading ACL rule '%s' for "
" the user named '%s': %s " ,
2019-02-04 10:58:35 -05:00
aclrules [ j ] , aclrules [ 0 ] , errmsg ) ;
2019-02-04 10:35:15 -05:00
return C_ERR ;
}
}
/* Having a disabled user in the configuration may be an error,
* warn about it without returning any error to the caller . */
if ( u - > flags & USER_FLAG_DISABLED ) {
serverLog ( LL_NOTICE , " The user '%s' is disabled (there is no "
" 'on' modifier in the user description). Make "
" sure this is not a configuration error. " ,
aclrules [ 0 ] ) ;
}
}
return C_OK ;
}
2019-02-06 06:39:11 -05:00
/* This function loads the ACL from the specified filename: every line
* is validated and shold be either empty or in the format used to specify
* users in the redis . conf configuration or in the ACL file , that is :
*
* user < username > . . . rules . . .
*
* Note that this function considers comments starting with ' # ' as errors
* because the ACL file is meant to be rewritten , and comments would be
* lost after the rewrite . Yet empty lines are allowed to avoid being too
* strict .
*
* One important part of implementing ACL LOAD , that uses this function , is
* to avoid ending with broken rules if the ACL file is invalid for some
* reason , so the function will attempt to validate the rules before loading
* each user . For every line that will be found broken the function will
2019-02-07 06:57:21 -05:00
* collect an error message .
*
* IMPORTANT : If there is at least a single error , nothing will be loaded
* and the rules will remain exactly as they were .
2019-02-06 06:39:11 -05:00
*
* At the end of the process , if no errors were found in the whole file then
* NULL is returned . Otherwise an SDS string describing in a single line
* a description of all the issues found is returned . */
sds ACLLoadFromFile ( const char * filename ) {
FILE * fp ;
char buf [ 1024 ] ;
/* Open the ACL file. */
if ( ( fp = fopen ( filename , " r " ) ) = = NULL ) {
sds errors = sdscatprintf ( sdsempty ( ) ,
" Error loading ACLs, opening file '%s': %s " ,
filename , strerror ( errno ) ) ;
return errors ;
}
/* Load the whole file as a single string in memory. */
sds acls = sdsempty ( ) ;
2019-02-07 10:53:35 -05:00
while ( fgets ( buf , sizeof ( buf ) , fp ) ! = NULL )
2019-02-06 06:39:11 -05:00
acls = sdscat ( acls , buf ) ;
fclose ( fp ) ;
/* Split the file into lines and attempt to load each line. */
int totlines ;
sds * lines , errors = sdsempty ( ) ;
lines = sdssplitlen ( acls , strlen ( acls ) , " \n " , 1 , & totlines ) ;
2019-02-06 10:44:55 -05:00
/* We need a fake user to validate the rules before making changes
* to the real user mentioned in the ACL line . */
user * fakeuser = ACLCreateUnlinkedUser ( ) ;
2019-02-07 06:57:21 -05:00
/* We do all the loading in a fresh insteance of the Users radix tree,
* so if there are errors loading the ACL file we can rollback to the
* old version . */
rax * old_users = Users ;
Users = raxNew ( ) ;
ACLInitDefaultUser ( ) ;
/* Load each line of the file. */
2019-02-06 06:39:11 -05:00
for ( int i = 0 ; i < totlines ; i + + ) {
sds * argv ;
int argc ;
int linenum = i + 1 ;
lines [ i ] = sdstrim ( lines [ i ] , " \t \r \n " ) ;
/* Skip blank lines */
if ( lines [ i ] [ 0 ] = = ' \0 ' ) continue ;
/* Split into arguments */
argv = sdssplitargs ( lines [ i ] , & argc ) ;
if ( argv = = NULL ) {
errors = sdscatprintf ( errors ,
2019-02-06 10:44:55 -05:00
" %d: unbalanced quotes in acl line. " ,
linenum ) ;
2019-02-06 06:39:11 -05:00
continue ;
}
/* Skip this line if the resulting command vector is empty. */
if ( argc = = 0 ) {
sdsfreesplitres ( argv , argc ) ;
continue ;
}
2019-02-06 10:44:55 -05:00
/* The line should start with the "user" keyword. */
2019-02-07 06:04:25 -05:00
if ( strcmp ( argv [ 0 ] , " user " ) | | argc < 2 ) {
2019-02-06 10:44:55 -05:00
errors = sdscatprintf ( errors ,
2019-02-07 06:04:25 -05:00
" %d: line should start with user keyword followed "
" by the username. " ,
2019-02-06 10:44:55 -05:00
linenum ) ;
2019-02-07 06:04:25 -05:00
sdsfreesplitres ( argv , argc ) ;
2019-02-06 10:44:55 -05:00
continue ;
}
/* Try to process the line using the fake user to validate iif
* the rules are able to apply cleanly . */
ACLSetUser ( fakeuser , " reset " , - 1 ) ;
int j ;
for ( j = 2 ; j < argc ; j + + ) {
if ( ACLSetUser ( fakeuser , argv [ j ] , sdslen ( argv [ j ] ) ) ! = C_OK ) {
char * errmsg = ACLSetUserStringError ( ) ;
errors = sdscatprintf ( errors ,
" %d: error in ACL: %s. " ,
linenum , errmsg ) ;
continue ;
}
}
2019-02-07 06:04:25 -05:00
if ( j ! = argc ) {
sdsfreesplitres ( argv , argc ) ;
continue ; /* Error in ACL rules, don't apply. */
}
/* We can finally lookup the user and apply the rule. If the
* user already exists we always reset it to start . */
user * u = ACLCreateUser ( argv [ 1 ] , sdslen ( argv [ 1 ] ) ) ;
if ( ! u ) {
u = ACLGetUserByName ( argv [ 1 ] , sdslen ( argv [ 1 ] ) ) ;
serverAssert ( u ! = NULL ) ;
ACLSetUser ( u , " reset " , - 1 ) ;
}
/* Note that the same rules already applied to the fake user, so
* we just assert that everything goess well : it should . */
for ( j = 2 ; j < argc ; j + + )
2019-02-07 06:20:30 -05:00
serverAssert ( ACLSetUser ( fakeuser , argv [ j ] , sdslen ( argv [ j ] ) ) = = C_OK ) ;
2019-02-06 10:44:55 -05:00
2019-02-07 06:04:25 -05:00
sdsfreesplitres ( argv , argc ) ;
2019-02-06 06:39:11 -05:00
}
2019-02-06 10:44:55 -05:00
ACLFreeUser ( fakeuser ) ;
2019-02-06 06:39:11 -05:00
sdsfreesplitres ( lines , totlines ) ;
2019-02-07 06:57:21 -05:00
2019-02-07 10:47:14 -05:00
/* Check if we found errors and react accordingly. */
2019-02-06 06:39:11 -05:00
if ( sdslen ( errors ) = = 0 ) {
2019-02-07 06:57:21 -05:00
/* The default user pointer is referenced in different places: instead
* of replacing such occurrences it is much simpler to copy the new
* default user configuration in the old one . */
user * new = ACLGetUserByName ( " default " , 7 ) ;
2019-02-07 10:47:14 -05:00
serverAssert ( new ! = NULL ) ;
2019-02-07 06:57:21 -05:00
ACLCopyUser ( DefaultUser , new ) ;
ACLFreeUser ( new ) ;
raxInsert ( Users , ( unsigned char * ) " default " , 7 , DefaultUser , NULL ) ;
2019-02-07 10:20:42 -05:00
raxRemove ( old_users , ( unsigned char * ) " default " , 7 , NULL ) ;
ACLFreeUsersSet ( old_users ) ;
2019-02-06 06:39:11 -05:00
sdsfree ( errors ) ;
2019-02-06 10:44:55 -05:00
return NULL ;
} else {
2019-02-07 06:57:21 -05:00
ACLFreeUsersSet ( Users ) ;
Users = old_users ;
errors = sdscat ( errors , " WARNING: ACL errors detected, no change to the previously active ACL rules was performed " ) ;
return errors ;
2019-02-06 06:39:11 -05:00
}
}
2019-01-10 10:35:55 -05:00
/* =============================================================================
* ACL related commands
* = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = */
2019-01-15 03:36:12 -05:00
/* ACL -- show and modify the configuration of ACL users.
2019-01-15 12:16:20 -05:00
* ACL HELP
* ACL LIST
* ACL SETUSER < username > . . . user attribs . . .
* ACL DELUSER < username >
* ACL GETUSER < username >
2019-01-15 03:36:12 -05:00
*/
void aclCommand ( client * c ) {
char * sub = c - > argv [ 1 ] - > ptr ;
if ( ! strcasecmp ( sub , " setuser " ) & & c - > argc > = 3 ) {
sds username = c - > argv [ 2 ] - > ptr ;
user * u = ACLGetUserByName ( username , sdslen ( username ) ) ;
if ( ! u ) u = ACLCreateUser ( username , sdslen ( username ) ) ;
serverAssert ( u ! = NULL ) ;
for ( int j = 3 ; j < c - > argc ; j + + ) {
2019-01-15 06:58:54 -05:00
if ( ACLSetUser ( u , c - > argv [ j ] - > ptr , sdslen ( c - > argv [ j ] - > ptr ) ) ! = C_OK ) {
2019-01-30 10:02:25 -05:00
char * errmsg = ACLSetUserStringError ( ) ;
2019-01-15 06:58:54 -05:00
addReplyErrorFormat ( c ,
2019-01-18 12:16:03 -05:00
" Error in ACL SETUSER modifier '%s': %s " ,
( char * ) c - > argv [ j ] - > ptr , errmsg ) ;
2019-01-15 03:36:12 -05:00
return ;
}
}
addReply ( c , shared . ok ) ;
2019-01-31 12:33:14 -05:00
} else if ( ! strcasecmp ( sub , " deluser " ) & & c - > argc > = 3 ) {
int deleted = 0 ;
for ( int j = 2 ; j < c - > argc ; j + + ) {
sds username = c - > argv [ j ] - > ptr ;
if ( ! strcmp ( username , " default " ) ) {
addReplyError ( c , " The 'default' user cannot be removed " ) ;
return ;
}
user * u ;
if ( raxRemove ( Users , ( unsigned char * ) username ,
sdslen ( username ) ,
( void * * ) & u ) )
{
/* When a user is deleted we need to cycle the active
* connections in order to kill all the pending ones that
* are authenticated with such user . */
ACLFreeUser ( u ) ;
listIter li ;
listNode * ln ;
listRewind ( server . clients , & li ) ;
while ( ( ln = listNext ( & li ) ) ! = NULL ) {
client * c = listNodeValue ( ln ) ;
if ( c - > user = = u ) {
/* We'll free the conenction asynchronously, so
* in theory to set a different user is not needed .
* However if there are bugs in Redis , soon or later
* this may result in some security hole : it ' s much
* more defensive to set the default user and put
* it in non authenticated mode . */
c - > user = DefaultUser ;
c - > authenticated = 0 ;
freeClientAsync ( c ) ;
}
}
deleted + + ;
}
}
addReplyLongLong ( c , deleted ) ;
2019-01-17 12:19:04 -05:00
} else if ( ! strcasecmp ( sub , " getuser " ) & & c - > argc = = 3 ) {
user * u = ACLGetUserByName ( c - > argv [ 2 ] - > ptr , sdslen ( c - > argv [ 2 ] - > ptr ) ) ;
2019-01-23 10:57:18 -05:00
if ( u = = NULL ) {
addReplyNull ( c ) ;
return ;
}
2019-01-30 09:52:36 -05:00
addReplyMapLen ( c , 4 ) ;
2019-01-17 12:19:04 -05:00
/* Flags */
addReplyBulkCString ( c , " flags " ) ;
void * deflen = addReplyDeferredLen ( c ) ;
int numflags = 0 ;
2019-01-31 10:49:22 -05:00
for ( int j = 0 ; ACLUserFlags [ j ] . flag ; j + + ) {
if ( u - > flags & ACLUserFlags [ j ] . flag ) {
addReplyBulkCString ( c , ACLUserFlags [ j ] . name ) ;
numflags + + ;
}
2019-01-17 12:19:04 -05:00
}
setDeferredSetLen ( c , deflen , numflags ) ;
/* Passwords */
addReplyBulkCString ( c , " passwords " ) ;
addReplyArrayLen ( c , listLength ( u - > passwords ) ) ;
listIter li ;
listNode * ln ;
listRewind ( u - > passwords , & li ) ;
while ( ( ln = listNext ( & li ) ) ) {
sds thispass = listNodeValue ( ln ) ;
addReplyBulkCBuffer ( c , thispass , sdslen ( thispass ) ) ;
}
2019-01-29 12:54:21 -05:00
/* Commands */
addReplyBulkCString ( c , " commands " ) ;
sds cmddescr = ACLDescribeUserCommandRules ( u ) ;
addReplyBulkSds ( c , cmddescr ) ;
2019-01-30 09:52:36 -05:00
/* Key patterns */
addReplyBulkCString ( c , " keys " ) ;
if ( u - > flags & USER_FLAG_ALLKEYS ) {
addReplyArrayLen ( c , 1 ) ;
addReplyBulkCBuffer ( c , " * " , 1 ) ;
} else {
addReplyArrayLen ( c , listLength ( u - > patterns ) ) ;
listIter li ;
listNode * ln ;
listRewind ( u - > patterns , & li ) ;
while ( ( ln = listNext ( & li ) ) ) {
sds thispat = listNodeValue ( ln ) ;
addReplyBulkCBuffer ( c , thispat , sdslen ( thispat ) ) ;
}
}
2019-01-31 12:32:49 -05:00
} else if ( ( ! strcasecmp ( sub , " list " ) | | ! strcasecmp ( sub , " users " ) ) & &
c - > argc = = 2 )
{
2019-01-31 11:01:28 -05:00
int justnames = ! strcasecmp ( sub , " users " ) ;
addReplyArrayLen ( c , raxSize ( Users ) ) ;
raxIterator ri ;
raxStart ( & ri , Users ) ;
raxSeek ( & ri , " ^ " , NULL , 0 ) ;
while ( raxNext ( & ri ) ) {
user * u = ri . data ;
if ( justnames ) {
addReplyBulkCBuffer ( c , u - > name , sdslen ( u - > name ) ) ;
} else {
/* Return information in the configuration file format. */
sds config = sdsnew ( " user " ) ;
config = sdscatsds ( config , u - > name ) ;
config = sdscatlen ( config , " " , 1 ) ;
sds descr = ACLDescribeUser ( u ) ;
config = sdscatsds ( config , descr ) ;
sdsfree ( descr ) ;
addReplyBulkSds ( c , config ) ;
}
}
raxStop ( & ri ) ;
} else if ( ! strcasecmp ( sub , " whoami " ) ) {
if ( c - > user ! = NULL ) {
addReplyBulkCBuffer ( c , c - > user - > name , sdslen ( c - > user - > name ) ) ;
} else {
addReplyNull ( c ) ;
}
2019-02-07 06:20:30 -05:00
} else if ( ! strcasecmp ( sub , " load " ) ) {
if ( server . acl_filename [ 0 ] = = ' \0 ' ) {
addReplyError ( c , " This Redis instance is not configured to use an ACL file. You may want to specify users via the ACL SETUSER command and then issue a CONFIG REWRITE (assuming you have a Redis configuration file set) in order to store users in the Redis configuration. " ) ;
return ;
} else {
sds errors = ACLLoadFromFile ( server . acl_filename ) ;
if ( errors = = NULL ) {
addReply ( c , shared . ok ) ;
} else {
addReplyError ( c , errors ) ;
sdsfree ( errors ) ;
}
}
2019-01-15 03:36:12 -05:00
} else if ( ! strcasecmp ( sub , " help " ) ) {
const char * help [ ] = {
2019-01-31 11:01:28 -05:00
" LIST -- Show user details in config file format. " ,
" USERS -- List all the registered usernames. " ,
2019-01-15 03:36:12 -05:00
" SETUSER <username> [attribs ...] -- Create or modify a user. " ,
" GETUSER <username> -- Get the user details. " ,
2019-01-31 11:01:28 -05:00
" DELUSER <username> -- Delete a user. " ,
" WHOAMI -- Return the current connection username. " ,
2019-01-15 03:36:12 -05:00
NULL
} ;
addReplyHelp ( c , help ) ;
} else {
addReplySubcommandSyntaxError ( c ) ;
}
}