Merge 7cd487c6eba9b8d10664fcc20d21b5d21e6271f3 into 26c6f1af9b29d525831c7fa9840ab3e47ed7b700
This commit is contained in:
commit
b297783750
175
src/acl.c
175
src/acl.c
@ -152,17 +152,6 @@ struct ACLUserFlag {
|
||||
{NULL, 0} /* Terminator. */
|
||||
};
|
||||
|
||||
struct ACLSelectorFlags {
|
||||
const char *name;
|
||||
uint64_t flag;
|
||||
} ACLSelectorFlags[] = {
|
||||
/* Note: the order here dictates the emitted order at ACLDescribeUser */
|
||||
{"allkeys", SELECTOR_FLAG_ALLKEYS},
|
||||
{"allchannels", SELECTOR_FLAG_ALLCHANNELS},
|
||||
{"allcommands", SELECTOR_FLAG_ALLCOMMANDS},
|
||||
{NULL, 0} /* Terminator. */
|
||||
};
|
||||
|
||||
/* ACL selectors are private and not exposed outside of acl.c. */
|
||||
typedef struct {
|
||||
uint32_t flags; /* See SELECTOR_FLAG_* */
|
||||
@ -193,11 +182,11 @@ typedef struct {
|
||||
* is used to regenerate the original ACL string for display. */
|
||||
} aclSelector;
|
||||
|
||||
void ACLResetFirstArgsForCommand(aclSelector *selector, unsigned long id);
|
||||
void ACLResetFirstArgs(aclSelector *selector);
|
||||
void ACLAddAllowedFirstArg(aclSelector *selector, unsigned long id, const char *sub);
|
||||
void ACLFreeLogEntry(void *le);
|
||||
int ACLSetSelector(aclSelector *selector, const char *op, size_t oplen);
|
||||
static void ACLResetFirstArgsForCommand(aclSelector *selector, unsigned long id);
|
||||
static void ACLResetFirstArgs(aclSelector *selector);
|
||||
static void ACLAddAllowedFirstArg(aclSelector *selector, unsigned long id, const char *sub);
|
||||
static void ACLFreeLogEntry(void *le);
|
||||
static int ACLSetSelector(aclSelector *selector, const char *op, size_t oplen);
|
||||
|
||||
/* The length of the string representation of a hashed password. */
|
||||
#define HASH_PASSWORD_LEN (SHA256_BLOCK_SIZE * 2)
|
||||
@ -211,7 +200,7 @@ int ACLSetSelector(aclSelector *selector, const char *op, size_t oplen);
|
||||
* information about the nature of the strings just monitoring the execution
|
||||
* time of the function. Note: The two strings must be the same length.
|
||||
*/
|
||||
int time_independent_strcmp(char *a, char *b, int len) {
|
||||
static int time_independent_strcmp(char *a, char *b, int len) {
|
||||
int diff = 0;
|
||||
for (int j = 0; j < len; j++) {
|
||||
diff |= (a[j] ^ b[j]);
|
||||
@ -221,7 +210,7 @@ int time_independent_strcmp(char *a, char *b, int len) {
|
||||
|
||||
/* Given an SDS string, returns the SHA256 hex representation as a
|
||||
* new SDS string. */
|
||||
sds ACLHashPassword(unsigned char *cleartext, size_t len) {
|
||||
static sds ACLHashPassword(unsigned char *cleartext, size_t len) {
|
||||
SHA256_CTX ctx;
|
||||
unsigned char hash[SHA256_BLOCK_SIZE];
|
||||
char hex[HASH_PASSWORD_LEN];
|
||||
@ -240,7 +229,7 @@ sds ACLHashPassword(unsigned char *cleartext, size_t len) {
|
||||
|
||||
/* Given a hash and the hash length, returns C_OK if it is a valid password
|
||||
* hash, or C_ERR otherwise. */
|
||||
int ACLCheckPasswordHash(unsigned char *hash, int hashlen) {
|
||||
static int ACLCheckPasswordHash(unsigned char *hash, int hashlen) {
|
||||
if (hashlen != HASH_PASSWORD_LEN) {
|
||||
return C_ERR;
|
||||
}
|
||||
@ -266,7 +255,7 @@ int ACLCheckPasswordHash(unsigned char *hash, int hashlen) {
|
||||
* ACL rules, presentation on ACL list, and to avoid subtle security bugs
|
||||
* that may arise from parsing the rules in presence of escapes.
|
||||
* The function returns 0 if the string has no spaces. */
|
||||
int ACLStringHasSpaces(const char *s, size_t len) {
|
||||
static int ACLStringHasSpaces(const char *s, size_t len) {
|
||||
for (size_t i = 0; i < len; i++) {
|
||||
if (isspace(s[i]) || s[i] == 0) return 1;
|
||||
}
|
||||
@ -287,19 +276,19 @@ uint64_t ACLGetCommandCategoryFlagByName(const char *name) {
|
||||
/* Method for searching for a user within a list of user definitions. The
|
||||
* list contains an array of user arguments, and we are only
|
||||
* searching the first argument, the username, for a match. */
|
||||
int ACLListMatchLoadedUser(void *definition, void *user) {
|
||||
static int ACLListMatchLoadedUser(void *definition, void *user) {
|
||||
sds *user_definition = definition;
|
||||
return sdscmp(user_definition[0], user) == 0;
|
||||
}
|
||||
|
||||
/* 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) {
|
||||
static int ACLListMatchSds(void *a, void *b) {
|
||||
return sdscmp(a, b) == 0;
|
||||
}
|
||||
|
||||
/* Method to duplicate list elements from ACL users password/patterns lists. */
|
||||
void *ACLListDupSds(void *item) {
|
||||
static void *ACLListDupSds(void *item) {
|
||||
return sdsdup(item);
|
||||
}
|
||||
|
||||
@ -311,7 +300,7 @@ typedef struct {
|
||||
} keyPattern;
|
||||
|
||||
/* Create a new key pattern. */
|
||||
keyPattern *ACLKeyPatternCreate(sds pattern, int flags) {
|
||||
static keyPattern *ACLKeyPatternCreate(sds pattern, int flags) {
|
||||
keyPattern *new = (keyPattern *)zmalloc(sizeof(keyPattern));
|
||||
new->pattern = pattern;
|
||||
new->flags = flags;
|
||||
@ -319,31 +308,31 @@ keyPattern *ACLKeyPatternCreate(sds pattern, int flags) {
|
||||
}
|
||||
|
||||
/* Free a key pattern and internal structures. */
|
||||
void ACLKeyPatternFree(keyPattern *pattern) {
|
||||
static void ACLKeyPatternFree(keyPattern *pattern) {
|
||||
sdsfree(pattern->pattern);
|
||||
zfree(pattern);
|
||||
}
|
||||
|
||||
/* Method for passwords/pattern comparison used for the user->passwords list
|
||||
* so that we can search for items with listSearchKey(). */
|
||||
int ACLListMatchKeyPattern(void *a, void *b) {
|
||||
static int ACLListMatchKeyPattern(void *a, void *b) {
|
||||
return sdscmp(((keyPattern *)a)->pattern, ((keyPattern *)b)->pattern) == 0;
|
||||
}
|
||||
|
||||
/* Method to free list elements from ACL users password/patterns lists. */
|
||||
void ACLListFreeKeyPattern(void *item) {
|
||||
static void ACLListFreeKeyPattern(void *item) {
|
||||
ACLKeyPatternFree(item);
|
||||
}
|
||||
|
||||
/* Method to duplicate list elements from ACL users password/patterns lists. */
|
||||
void *ACLListDupKeyPattern(void *item) {
|
||||
static void *ACLListDupKeyPattern(void *item) {
|
||||
keyPattern *old = (keyPattern *)item;
|
||||
return ACLKeyPatternCreate(sdsdup(old->pattern), old->flags);
|
||||
}
|
||||
|
||||
/* Append the string representation of a key pattern onto the
|
||||
* provided base string. */
|
||||
sds sdsCatPatternString(sds base, keyPattern *pat) {
|
||||
static sds sdsCatPatternString(sds base, keyPattern *pat) {
|
||||
if (pat->flags == ACL_ALL_PERMISSION) {
|
||||
base = sdscatlen(base, "~", 1);
|
||||
} else if (pat->flags == ACL_READ_PERMISSION) {
|
||||
@ -358,7 +347,7 @@ sds sdsCatPatternString(sds base, keyPattern *pat) {
|
||||
|
||||
/* Create an empty selector with the provided set of initial
|
||||
* flags. The selector will be default have no permissions. */
|
||||
aclSelector *ACLCreateSelector(int flags) {
|
||||
static aclSelector *ACLCreateSelector(int flags) {
|
||||
aclSelector *selector = zmalloc(sizeof(aclSelector));
|
||||
selector->flags = flags | server.acl_pubsub_default;
|
||||
selector->patterns = listCreate();
|
||||
@ -378,7 +367,7 @@ aclSelector *ACLCreateSelector(int flags) {
|
||||
}
|
||||
|
||||
/* Cleanup the provided selector, including all interior structures. */
|
||||
void ACLFreeSelector(aclSelector *selector) {
|
||||
static void ACLFreeSelector(aclSelector *selector) {
|
||||
listRelease(selector->patterns);
|
||||
listRelease(selector->channels);
|
||||
sdsfree(selector->command_rules);
|
||||
@ -387,7 +376,7 @@ void ACLFreeSelector(aclSelector *selector) {
|
||||
}
|
||||
|
||||
/* Create an exact copy of the provided selector. */
|
||||
aclSelector *ACLCopySelector(aclSelector *src) {
|
||||
static aclSelector *ACLCopySelector(aclSelector *src) {
|
||||
aclSelector *dst = zmalloc(sizeof(aclSelector));
|
||||
dst->flags = src->flags;
|
||||
dst->patterns = listDup(src->patterns);
|
||||
@ -408,19 +397,19 @@ aclSelector *ACLCopySelector(aclSelector *src) {
|
||||
}
|
||||
|
||||
/* List method for freeing a selector */
|
||||
void ACLListFreeSelector(void *a) {
|
||||
static void ACLListFreeSelector(void *a) {
|
||||
ACLFreeSelector((aclSelector *)a);
|
||||
}
|
||||
|
||||
/* List method for duplicating a selector */
|
||||
void *ACLListDuplicateSelector(void *src) {
|
||||
static void *ACLListDuplicateSelector(void *src) {
|
||||
return ACLCopySelector((aclSelector *)src);
|
||||
}
|
||||
|
||||
/* All users have an implicit root selector which
|
||||
* provides backwards compatibility to the old ACLs-
|
||||
* permissions. */
|
||||
aclSelector *ACLUserGetRootSelector(user *u) {
|
||||
static aclSelector *ACLUserGetRootSelector(user *u) {
|
||||
serverAssert(listLength(u->selectors));
|
||||
aclSelector *s = (aclSelector *)listNodeValue(listFirst(u->selectors));
|
||||
serverAssert(s->flags & SELECTOR_FLAG_ROOT);
|
||||
@ -432,7 +421,7 @@ aclSelector *ACLUserGetRootSelector(user *u) {
|
||||
* the structure representing the user.
|
||||
*
|
||||
* If the user with such name already exists NULL is returned. */
|
||||
user *ACLCreateUser(const char *name, size_t namelen) {
|
||||
static user *ACLCreateUser(const char *name, size_t namelen) {
|
||||
if (raxFind(Users, (unsigned char *)name, namelen, NULL)) return NULL;
|
||||
user *u = zmalloc(sizeof(*u));
|
||||
u->name = sdsnewlen(name, namelen);
|
||||
@ -523,7 +512,7 @@ void ACLFreeUserAndKillClients(user *u) {
|
||||
/* 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) {
|
||||
static void ACLCopyUser(user *dst, user *src) {
|
||||
listRelease(dst->passwords);
|
||||
listRelease(dst->selectors);
|
||||
dst->passwords = listDup(src->passwords);
|
||||
@ -545,7 +534,7 @@ void ACLCopyUser(user *dst, user *src) {
|
||||
* 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. */
|
||||
int ACLGetCommandBitCoordinates(uint64_t id, uint64_t *word, uint64_t *bit) {
|
||||
static int ACLGetCommandBitCoordinates(uint64_t id, uint64_t *word, uint64_t *bit) {
|
||||
if (id >= USER_COMMAND_BITS_COUNT) return C_ERR;
|
||||
*word = id / sizeof(uint64_t) / 8;
|
||||
*bit = 1ULL << (id % (sizeof(uint64_t) * 8));
|
||||
@ -559,7 +548,7 @@ int ACLGetCommandBitCoordinates(uint64_t id, uint64_t *word, uint64_t *bit) {
|
||||
*
|
||||
* If the bit overflows the user internal representation, zero is returned
|
||||
* in order to disallow the execution of the command in such edge case. */
|
||||
int ACLGetSelectorCommandBit(const aclSelector *selector, unsigned long id) {
|
||||
static int ACLGetSelectorCommandBit(const aclSelector *selector, unsigned long id) {
|
||||
uint64_t word, bit;
|
||||
if (ACLGetCommandBitCoordinates(id, &word, &bit) == C_ERR) return 0;
|
||||
return (selector->allowed_commands[word] & bit) != 0;
|
||||
@ -568,7 +557,7 @@ int ACLGetSelectorCommandBit(const aclSelector *selector, unsigned long id) {
|
||||
/* 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 ACLSelectorCanExecuteFutureCommands(aclSelector *selector) {
|
||||
static int ACLSelectorCanExecuteFutureCommands(aclSelector *selector) {
|
||||
return ACLGetSelectorCommandBit(selector, USER_COMMAND_BITS_COUNT - 1);
|
||||
}
|
||||
|
||||
@ -577,7 +566,7 @@ int ACLSelectorCanExecuteFutureCommands(aclSelector *selector) {
|
||||
* 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. */
|
||||
void ACLSetSelectorCommandBit(aclSelector *selector, unsigned long id, int value) {
|
||||
static void ACLSetSelectorCommandBit(aclSelector *selector, unsigned long id, int value) {
|
||||
uint64_t word, bit;
|
||||
if (ACLGetCommandBitCoordinates(id, &word, &bit) == C_ERR) return;
|
||||
if (value) {
|
||||
@ -591,7 +580,7 @@ void ACLSetSelectorCommandBit(aclSelector *selector, unsigned long id, int value
|
||||
/* Remove a rule from the retained command rules. Always match rules
|
||||
* verbatim, but also remove subcommand rules if we are adding or removing the
|
||||
* entire command. */
|
||||
void ACLSelectorRemoveCommandRule(aclSelector *selector, sds new_rule) {
|
||||
static void ACLSelectorRemoveCommandRule(aclSelector *selector, sds new_rule) {
|
||||
size_t new_len = sdslen(new_rule);
|
||||
char *existing_rule = selector->command_rules;
|
||||
|
||||
@ -637,7 +626,7 @@ void ACLSelectorRemoveCommandRule(aclSelector *selector, sds new_rule) {
|
||||
|
||||
/* This function is responsible for updating the command_rules struct so that relative ordering of
|
||||
* commands and categories is maintained and can be reproduced without loss. */
|
||||
void ACLUpdateCommandRules(aclSelector *selector, const char *rule, int allow) {
|
||||
static void ACLUpdateCommandRules(aclSelector *selector, const char *rule, int allow) {
|
||||
sds new_rule = sdsnew(rule);
|
||||
sdstolower(new_rule);
|
||||
|
||||
@ -649,7 +638,7 @@ void ACLUpdateCommandRules(aclSelector *selector, const char *rule, int allow) {
|
||||
|
||||
/* This function is used to allow/block a specific command.
|
||||
* Allowing/blocking a container command also applies for its subcommands */
|
||||
void ACLChangeSelectorPerm(aclSelector *selector, struct serverCommand *cmd, int allow) {
|
||||
static void ACLChangeSelectorPerm(aclSelector *selector, struct serverCommand *cmd, int allow) {
|
||||
unsigned long id = cmd->id;
|
||||
ACLSetSelectorCommandBit(selector, id, allow);
|
||||
ACLResetFirstArgsForCommand(selector, id);
|
||||
@ -671,7 +660,7 @@ void ACLChangeSelectorPerm(aclSelector *selector, struct serverCommand *cmd, int
|
||||
* 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. */
|
||||
void ACLSetSelectorCommandBitsForCategory(hashtable *commands, aclSelector *selector, uint64_t cflag, int value) {
|
||||
static void ACLSetSelectorCommandBitsForCategory(hashtable *commands, aclSelector *selector, uint64_t cflag, int value) {
|
||||
hashtableIterator iter;
|
||||
hashtableInitIterator(&iter, commands, 0);
|
||||
void *next;
|
||||
@ -724,7 +713,7 @@ void ACLRecomputeCommandBitsFromCommandRulesAllUsers(void) {
|
||||
raxStop(&ri);
|
||||
}
|
||||
|
||||
int ACLSetSelectorCategory(aclSelector *selector, const char *category, int allow) {
|
||||
static int ACLSetSelectorCategory(aclSelector *selector, const char *category, int allow) {
|
||||
uint64_t cflag = ACLGetCommandCategoryFlagByName(category + 1);
|
||||
if (!cflag) return C_ERR;
|
||||
|
||||
@ -735,42 +724,6 @@ int ACLSetSelectorCategory(aclSelector *selector, const char *category, int allo
|
||||
return C_OK;
|
||||
}
|
||||
|
||||
void ACLCountCategoryBitsForCommands(hashtable *commands,
|
||||
aclSelector *selector,
|
||||
unsigned long *on,
|
||||
unsigned long *off,
|
||||
uint64_t cflag) {
|
||||
hashtableIterator iter;
|
||||
hashtableInitIterator(&iter, commands, 0);
|
||||
void *next;
|
||||
while (hashtableNext(&iter, &next)) {
|
||||
struct serverCommand *cmd = next;
|
||||
if (cmd->acl_categories & cflag) {
|
||||
if (ACLGetSelectorCommandBit(selector, cmd->id))
|
||||
(*on)++;
|
||||
else
|
||||
(*off)++;
|
||||
}
|
||||
if (cmd->subcommands_ht) {
|
||||
ACLCountCategoryBitsForCommands(cmd->subcommands_ht, selector, on, off, cflag);
|
||||
}
|
||||
}
|
||||
hashtableResetIterator(&iter);
|
||||
}
|
||||
|
||||
/* 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 category name is not valid, C_ERR is returned, otherwise C_OK is
|
||||
* returned and on and off are populated by reference. */
|
||||
int ACLCountCategoryBitsForSelector(aclSelector *selector, unsigned long *on, unsigned long *off, const char *category) {
|
||||
uint64_t cflag = ACLGetCommandCategoryFlagByName(category);
|
||||
if (!cflag) return C_ERR;
|
||||
|
||||
*on = *off = 0;
|
||||
ACLCountCategoryBitsForCommands(server.orig_commands, selector, on, off, cflag);
|
||||
return C_OK;
|
||||
}
|
||||
|
||||
/* This function returns an SDS string representing the specified selector 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
|
||||
@ -778,7 +731,7 @@ int ACLCountCategoryBitsForSelector(aclSelector *selector, unsigned long *on, un
|
||||
* 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 ACLDescribeSelectorCommandRules(aclSelector *selector) {
|
||||
static sds ACLDescribeSelectorCommandRules(aclSelector *selector) {
|
||||
sds rules = sdsempty();
|
||||
|
||||
/* We use this fake selector as a "sanity" check to make sure the rules
|
||||
@ -828,7 +781,7 @@ sds ACLDescribeSelectorCommandRules(aclSelector *selector) {
|
||||
return rules;
|
||||
}
|
||||
|
||||
sds ACLDescribeSelector(aclSelector *selector) {
|
||||
static sds ACLDescribeSelector(aclSelector *selector) {
|
||||
listIter li;
|
||||
listNode *ln;
|
||||
sds res = sdsempty();
|
||||
@ -921,7 +874,7 @@ robj *ACLDescribeUser(user *u) {
|
||||
/* 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 serverCommand *ACLLookupCommand(const char *name) {
|
||||
static struct serverCommand *ACLLookupCommand(const char *name) {
|
||||
struct serverCommand *cmd;
|
||||
sds sdsname = sdsnew(name);
|
||||
cmd = lookupCommandBySdsLogic(server.orig_commands, sdsname);
|
||||
@ -931,7 +884,7 @@ struct serverCommand *ACLLookupCommand(const char *name) {
|
||||
|
||||
/* Flush the array of allowed first-args for the specified user
|
||||
* and command ID. */
|
||||
void ACLResetFirstArgsForCommand(aclSelector *selector, unsigned long id) {
|
||||
static void ACLResetFirstArgsForCommand(aclSelector *selector, unsigned long id) {
|
||||
if (selector->allowed_firstargs && selector->allowed_firstargs[id]) {
|
||||
for (int i = 0; selector->allowed_firstargs[id][i]; i++) sdsfree(selector->allowed_firstargs[id][i]);
|
||||
zfree(selector->allowed_firstargs[id]);
|
||||
@ -942,7 +895,7 @@ void ACLResetFirstArgsForCommand(aclSelector *selector, unsigned long id) {
|
||||
/* Flush the entire table of first-args. This is useful on +@all, -@all
|
||||
* or similar to return back to the minimal memory usage (and checks to do)
|
||||
* for the user. */
|
||||
void ACLResetFirstArgs(aclSelector *selector) {
|
||||
static void ACLResetFirstArgs(aclSelector *selector) {
|
||||
if (selector->allowed_firstargs == NULL) return;
|
||||
for (int j = 0; j < USER_COMMAND_BITS_COUNT; j++) {
|
||||
if (selector->allowed_firstargs[j]) {
|
||||
@ -956,7 +909,7 @@ void ACLResetFirstArgs(aclSelector *selector) {
|
||||
|
||||
/* Add a first-arg to the list of subcommands for the user 'u' and
|
||||
* the command id specified. */
|
||||
void ACLAddAllowedFirstArg(aclSelector *selector, unsigned long id, const char *sub) {
|
||||
static void ACLAddAllowedFirstArg(aclSelector *selector, unsigned long id, const char *sub) {
|
||||
/* If this is the first first-arg to be configured for
|
||||
* this user, we have to allocate the first-args array. */
|
||||
if (selector->allowed_firstargs == NULL) {
|
||||
@ -989,7 +942,7 @@ void ACLAddAllowedFirstArg(aclSelector *selector, unsigned long id, const char *
|
||||
*
|
||||
* If any of the operations are invalid, NULL will be returned instead
|
||||
* and errno will be set corresponding to the interior error. */
|
||||
aclSelector *aclCreateSelectorFromOpSet(const char *opset, size_t opsetlen) {
|
||||
static aclSelector *aclCreateSelectorFromOpSet(const char *opset, size_t opsetlen) {
|
||||
serverAssert(opset[0] == '(' && opset[opsetlen - 1] == ')');
|
||||
aclSelector *s = ACLCreateSelector(0);
|
||||
|
||||
@ -1000,11 +953,10 @@ aclSelector *aclCreateSelectorFromOpSet(const char *opset, size_t opsetlen) {
|
||||
if (ACLSetSelector(s, argv[i], sdslen(argv[i])) == C_ERR) {
|
||||
ACLFreeSelector(s);
|
||||
s = NULL;
|
||||
goto cleanup;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
cleanup:
|
||||
sdsfreesplitres(argv, argc);
|
||||
sdsfree(trimmed);
|
||||
return s;
|
||||
@ -1047,7 +999,7 @@ cleanup:
|
||||
* allchannels Alias for &*
|
||||
* resetchannels Flush the list of allowed channel patterns.
|
||||
*/
|
||||
int ACLSetSelector(aclSelector *selector, const char *op, size_t oplen) {
|
||||
static int ACLSetSelector(aclSelector *selector, const char *op, size_t oplen) {
|
||||
if (!strcasecmp(op, "allkeys") || !strcasecmp(op, "~*")) {
|
||||
selector->flags |= SELECTOR_FLAG_ALLKEYS;
|
||||
listEmpty(selector->patterns);
|
||||
@ -1423,7 +1375,7 @@ const char *ACLSetUserStringError(void) {
|
||||
}
|
||||
|
||||
/* Create the default user, this has special permissions. */
|
||||
user *ACLCreateDefaultUser(void) {
|
||||
static user *ACLCreateDefaultUser(void) {
|
||||
user *new = ACLCreateUser("default", 7);
|
||||
ACLSetUser(new, "+@all", -1);
|
||||
ACLSetUser(new, "~*", -1);
|
||||
@ -1501,7 +1453,7 @@ void addAuthErrReply(client *c, robj *err) {
|
||||
* connection user reference is populated.
|
||||
*
|
||||
* The return value is AUTH_OK on success (valid username / password pair) & AUTH_ERR otherwise. */
|
||||
int checkPasswordBasedAuth(client *c, robj *username, robj *password) {
|
||||
static int checkPasswordBasedAuth(client *c, robj *username, robj *password) {
|
||||
if (ACLCheckUserCredentials(username, password) == C_OK) {
|
||||
c->flag.authenticated = 1;
|
||||
c->user = ACLGetUserByName(username->ptr, sdslen(username->ptr));
|
||||
@ -1565,13 +1517,6 @@ unsigned long ACLGetCommandID(sds cmdname) {
|
||||
return thisid;
|
||||
}
|
||||
|
||||
/* Clear command id table and reset nextid to 0. */
|
||||
void ACLClearCommandID(void) {
|
||||
if (commandId) raxFree(commandId);
|
||||
commandId = NULL;
|
||||
nextid = 0;
|
||||
}
|
||||
|
||||
/* Return an username by its name, or NULL if the user does not exist. */
|
||||
user *ACLGetUserByName(const char *name, size_t namelen) {
|
||||
void *myuser = NULL;
|
||||
@ -1674,11 +1619,11 @@ typedef struct {
|
||||
getKeysResult keys;
|
||||
} aclKeyResultCache;
|
||||
|
||||
void initACLKeyResultCache(aclKeyResultCache *cache) {
|
||||
static void initACLKeyResultCache(aclKeyResultCache *cache) {
|
||||
cache->keys_init = 0;
|
||||
}
|
||||
|
||||
void cleanupACLKeyResultCache(aclKeyResultCache *cache) {
|
||||
static void cleanupACLKeyResultCache(aclKeyResultCache *cache) {
|
||||
if (cache->keys_init) getKeysFreeResult(&(cache->keys));
|
||||
}
|
||||
|
||||
@ -1897,7 +1842,7 @@ int ACLCheckAllPerm(client *c, int *idxptr) {
|
||||
|
||||
/* If 'new' can access all channels 'original' could then return NULL;
|
||||
Otherwise return a list of channels that the new user can access */
|
||||
list *getUpcomingChannelList(user *new, user *original) {
|
||||
static list *getUpcomingChannelList(user *new, user *original) {
|
||||
listIter li, lpi;
|
||||
listNode *ln, *lpn;
|
||||
|
||||
@ -1954,7 +1899,7 @@ list *getUpcomingChannelList(user *new, user *original) {
|
||||
|
||||
/* Check if the client should be killed because it is subscribed to channels that were
|
||||
* permitted in the past, are not in the `upcoming` channel list. */
|
||||
int ACLShouldKillPubsubClient(client *c, list *upcoming) {
|
||||
static int ACLShouldKillPubsubClient(client *c, list *upcoming) {
|
||||
robj *o;
|
||||
int kill = 0;
|
||||
|
||||
@ -2001,7 +1946,7 @@ int ACLShouldKillPubsubClient(client *c, list *upcoming) {
|
||||
|
||||
/* Check if the user's existing pub/sub clients violate the ACL pub/sub
|
||||
* permissions specified via the upcoming argument, and kill them if so. */
|
||||
void ACLKillPubsubClientsIfNeeded(user *new, user *original) {
|
||||
static void ACLKillPubsubClientsIfNeeded(user *new, user *original) {
|
||||
/* Do nothing if there are no subscribers. */
|
||||
if (pubsubTotalSubscriptions() == 0) return;
|
||||
|
||||
@ -2041,7 +1986,7 @@ void ACLKillPubsubClientsIfNeeded(user *new, user *original) {
|
||||
* that are untouched are still duplicated. If there is an unmatched parenthesis, NULL
|
||||
* is returned and invalid_idx is set to the argument with the start of the opening
|
||||
* parenthesis. */
|
||||
sds *ACLMergeSelectorArguments(sds *argv, int argc, int *merged_argc, int *invalid_idx) {
|
||||
static sds *ACLMergeSelectorArguments(sds *argv, int argc, int *merged_argc, int *invalid_idx) {
|
||||
*merged_argc = 0;
|
||||
int open_bracket_start = -1;
|
||||
|
||||
@ -2212,7 +2157,7 @@ int ACLAppendUserForLoading(sds *argv, int argc, int *argc_err) {
|
||||
/* 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) {
|
||||
static int ACLLoadConfiguredUsers(void) {
|
||||
listIter li;
|
||||
listNode *ln;
|
||||
listRewind(UsersToLoad, &li);
|
||||
@ -2281,7 +2226,7 @@ int ACLLoadConfiguredUsers(void) {
|
||||
* 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) {
|
||||
static sds ACLLoadFromFile(const char *filename) {
|
||||
FILE *fp;
|
||||
char buf[1024];
|
||||
|
||||
@ -2467,7 +2412,7 @@ sds ACLLoadFromFile(const char *filename) {
|
||||
/* Generate a copy of the ACLs currently in memory in the specified filename.
|
||||
* Returns C_OK on success or C_ERR if there was an error during the I/O.
|
||||
* When C_ERR is returned a log is produced with hints about the issue. */
|
||||
int ACLSaveToFile(const char *filename) {
|
||||
static int ACLSaveToFile(const char *filename) {
|
||||
sds acl = sdsempty();
|
||||
int fd = -1;
|
||||
sds tmpfilename = NULL;
|
||||
@ -2595,7 +2540,7 @@ typedef struct ACLLogEntry {
|
||||
/* This function will check if ACL entries 'a' and 'b' are similar enough
|
||||
* that we should actually update the existing entry in our ACL log instead
|
||||
* of creating a new one. */
|
||||
int ACLLogMatchEntry(ACLLogEntry *a, ACLLogEntry *b) {
|
||||
static int ACLLogMatchEntry(ACLLogEntry *a, ACLLogEntry *b) {
|
||||
if (a->reason != b->reason) return 0;
|
||||
if (a->context != b->context) return 0;
|
||||
mstime_t delta = a->ctime - b->ctime;
|
||||
@ -2607,7 +2552,7 @@ int ACLLogMatchEntry(ACLLogEntry *a, ACLLogEntry *b) {
|
||||
}
|
||||
|
||||
/* Release an ACL log entry. */
|
||||
void ACLFreeLogEntry(void *leptr) {
|
||||
static void ACLFreeLogEntry(void *leptr) {
|
||||
ACLLogEntry *le = leptr;
|
||||
sdsfree(le->object);
|
||||
sdsfree(le->username);
|
||||
@ -2616,7 +2561,7 @@ void ACLFreeLogEntry(void *leptr) {
|
||||
}
|
||||
|
||||
/* Update the relevant counter by the reason */
|
||||
void ACLUpdateInfoMetrics(int reason) {
|
||||
static void ACLUpdateInfoMetrics(int reason) {
|
||||
if (reason == ACL_DENIED_AUTH) {
|
||||
server.acl_info.user_auth_failures++;
|
||||
} else if (reason == ACL_DENIED_CMD) {
|
||||
@ -2763,7 +2708,7 @@ sds getAclErrorMessage(int acl_res, user *user, struct serverCommand *cmd, sds e
|
||||
* ==========================================================================*/
|
||||
|
||||
/* ACL CAT category */
|
||||
void aclCatWithFlags(client *c, hashtable *commands, uint64_t cflag, int *arraylen) {
|
||||
static void aclCatWithFlags(client *c, hashtable *commands, uint64_t cflag, int *arraylen) {
|
||||
hashtableIterator iter;
|
||||
hashtableInitIterator(&iter, commands, 0);
|
||||
void *next;
|
||||
@ -2787,7 +2732,7 @@ void aclCatWithFlags(client *c, hashtable *commands, uint64_t cflag, int *arrayl
|
||||
* Setting verbose to 1 means that the full qualifier for key and channel
|
||||
* permissions are shown.
|
||||
*/
|
||||
int aclAddReplySelectorDescription(client *c, aclSelector *s) {
|
||||
static int aclAddReplySelectorDescription(client *c, aclSelector *s) {
|
||||
listIter li;
|
||||
listNode *ln;
|
||||
|
||||
|
@ -542,8 +542,10 @@ void loadServerConfigFromString(char *config) {
|
||||
|
||||
/* Otherwise we re-add the command under a different name. */
|
||||
if (sdslen(argv[2]) != 0) {
|
||||
sdsfree(cmd->fullname);
|
||||
cmd->fullname = sdsdup(argv[2]);
|
||||
if (cmd->current_name != cmd->fullname) {
|
||||
sdsfree(cmd->current_name);
|
||||
}
|
||||
cmd->current_name = sdsdup(argv[2]);
|
||||
if (!hashtableAdd(server.commands, cmd)) {
|
||||
err = "Target command name already exists";
|
||||
goto loaderr;
|
||||
|
@ -1403,6 +1403,7 @@ ValkeyModuleCommand *moduleCreateCommandProxy(struct ValkeyModule *module,
|
||||
cp->serverCmd = zcalloc(sizeof(*serverCmd));
|
||||
cp->serverCmd->declared_name = declared_name; /* SDS for module commands */
|
||||
cp->serverCmd->fullname = fullname;
|
||||
cp->serverCmd->current_name = fullname;
|
||||
cp->serverCmd->group = COMMAND_GROUP_MODULE;
|
||||
cp->serverCmd->proc = ValkeyModuleCommandDispatcher;
|
||||
cp->serverCmd->flags = flags | CMD_MODULE;
|
||||
|
20
src/server.c
20
src/server.c
@ -519,7 +519,12 @@ int hashtableResizeAllowed(size_t moreMem, double usedRatio) {
|
||||
return !overMaxmemoryAfterAlloc(moreMem);
|
||||
}
|
||||
|
||||
const void *hashtableCommandGetKey(const void *element) {
|
||||
const void *hashtableCommandGetCurrentName(const void *element) {
|
||||
struct serverCommand *command = (struct serverCommand *)element;
|
||||
return command->current_name;
|
||||
}
|
||||
|
||||
const void *hashtableCommandGetOriginalName(const void *element) {
|
||||
struct serverCommand *command = (struct serverCommand *)element;
|
||||
return command->fullname;
|
||||
}
|
||||
@ -624,12 +629,18 @@ hashtableType kvstoreExpiresHashtableType = {
|
||||
.getMetadataSize = kvstoreHashtableMetadataSize,
|
||||
};
|
||||
|
||||
/* Command set, hashed by sds string, stores serverCommand structs. */
|
||||
hashtableType commandSetType = {.entryGetKey = hashtableCommandGetKey,
|
||||
/* Command set, hashed by current command name, stores serverCommand structs. */
|
||||
hashtableType commandSetType = {.entryGetKey = hashtableCommandGetCurrentName,
|
||||
.hashFunction = dictSdsCaseHash,
|
||||
.keyCompare = hashtableStringKeyCaseCompare,
|
||||
.instant_rehashing = 1};
|
||||
|
||||
/* Command set, hashed by original command name, stores serverCommand structs. */
|
||||
hashtableType originalCommandSetType = {.entryGetKey = hashtableCommandGetOriginalName,
|
||||
.hashFunction = dictSdsCaseHash,
|
||||
.keyCompare = hashtableStringKeyCaseCompare,
|
||||
.instant_rehashing = 1};
|
||||
|
||||
/* Sub-command set, hashed by char* string, stores serverCommand structs. */
|
||||
hashtableType subcommandSetType = {.entryGetKey = hashtableSubcommandGetKey,
|
||||
.hashFunction = dictCStrCaseHash,
|
||||
@ -2280,7 +2291,7 @@ void initServerConfig(void) {
|
||||
* initial configuration, since command names may be changed via
|
||||
* valkey.conf using the rename-command directive. */
|
||||
server.commands = hashtableCreate(&commandSetType);
|
||||
server.orig_commands = hashtableCreate(&commandSetType);
|
||||
server.orig_commands = hashtableCreate(&originalCommandSetType);
|
||||
populateCommandTable();
|
||||
|
||||
/* Debugging */
|
||||
@ -3203,6 +3214,7 @@ void populateCommandTable(void) {
|
||||
int retval1, retval2;
|
||||
|
||||
c->fullname = sdsnew(c->declared_name);
|
||||
c->current_name = c->fullname;
|
||||
if (populateCommandStructure(c) == C_ERR) continue;
|
||||
|
||||
retval1 = hashtableAdd(server.commands, c);
|
||||
|
15
src/server.h
15
src/server.h
@ -2459,12 +2459,13 @@ struct serverCommand {
|
||||
|
||||
/* Runtime populated data */
|
||||
long long microseconds, calls, rejected_calls, failed_calls;
|
||||
int id; /* Command ID. This is a progressive ID starting from 0 that
|
||||
is assigned at runtime, and is used in order to check
|
||||
ACLs. A connection is able to execute a given command if
|
||||
the user associated to the connection has this command
|
||||
bit set in the bitmap of allowed commands. */
|
||||
sds fullname; /* A SDS string representing the command fullname. */
|
||||
int id; /* Command ID. This is a progressive ID starting from 0 that
|
||||
is assigned at runtime, and is used in order to check
|
||||
ACLs. A connection is able to execute a given command if
|
||||
the user associated to the connection has this command
|
||||
bit set in the bitmap of allowed commands. */
|
||||
sds fullname; /* Includes parent name if any: "parentcmd|childcmd". Unchanged if command is renamed. */
|
||||
sds current_name; /* Same as fullname, becomes a separate string if command is renamed. */
|
||||
struct hdr_histogram
|
||||
*latency_histogram; /* Points to the command latency command histogram (unit of time nanosecond). */
|
||||
keySpec legacy_range_key_spec; /* The legacy (first,last,step) key spec is
|
||||
@ -3041,7 +3042,6 @@ int ACLAuthenticateUser(client *c, robj *username, robj *password, robj **err);
|
||||
int checkModuleAuthentication(client *c, robj *username, robj *password, robj **err);
|
||||
void addAuthErrReply(client *c, robj *err);
|
||||
unsigned long ACLGetCommandID(sds cmdname);
|
||||
void ACLClearCommandID(void);
|
||||
user *ACLGetUserByName(const char *name, size_t namelen);
|
||||
int ACLUserCheckKeyPerm(user *u, const char *key, int keylen, int flags);
|
||||
int ACLUserCheckChannelPerm(user *u, sds channel, int literal);
|
||||
@ -3055,7 +3055,6 @@ int ACLAddCommandCategory(const char *name, uint64_t flag);
|
||||
void ACLCleanupCategoriesOnFailure(size_t num_acl_categories_added);
|
||||
int ACLAppendUserForLoading(sds *argv, int argc, int *argc_err);
|
||||
const char *ACLSetUserStringError(void);
|
||||
int ACLLoadConfiguredUsers(void);
|
||||
robj *ACLDescribeUser(user *u);
|
||||
void ACLLoadUsersAtStartup(void);
|
||||
void addReplyCommandCategories(client *c, struct serverCommand *cmd);
|
||||
|
@ -1244,3 +1244,67 @@ start_server {overrides {user "default on nopass ~* +@all -flushdb"} tags {acl e
|
||||
}
|
||||
}
|
||||
|
||||
tags {acl external:skip} {
|
||||
set renames [list rename-command "PING RainWasHere" \
|
||||
rename-command "RainWasHere VPING" \
|
||||
rename-command "INCR VINCR" \
|
||||
rename-command "SET VSET" \
|
||||
rename-command "SADD VSADD" \
|
||||
rename-command "HGET VHGET" \
|
||||
rename-command "HSET VHSET" \
|
||||
rename-command "CONFIG VCONFIG" ]
|
||||
start_server [list config_lines $renames ] {
|
||||
test {ACL still denies individual renamed commands} {
|
||||
r ACL setuser newuser on nopass ~* +acl -ping +incr
|
||||
r AUTH newuser anypass
|
||||
r VINCR mycounter ; # Should not raise an error
|
||||
catch {r vping} e
|
||||
set e
|
||||
} {*NOPERM*ping*}
|
||||
|
||||
test {ACL command classes aren't affected by command renaming} {
|
||||
r ACL setuser newuser -@all +@set +acl
|
||||
r VSADD myset a b c; # Should not raise an error
|
||||
r ACL setuser newuser +@all -@string
|
||||
r VSADD myset a b c; # Again should not raise an error
|
||||
# String commands instead should raise an error
|
||||
catch {r vset foo bar} e
|
||||
r ACL setuser newuser allcommands; # Undo commands ACL
|
||||
set e
|
||||
} {*NOPERM*set*}
|
||||
|
||||
test {ACL GETUSER provides correct results when commands renamed} {
|
||||
r ACL SETUSER adv-test
|
||||
r ACL SETUSER adv-test +@all -@hash -@slow +hget
|
||||
assert_equal "+@all -@hash -@slow +hget" [dict get [r ACL getuser adv-test] commands]
|
||||
|
||||
# Categories are re-ordered if re-added
|
||||
r ACL SETUSER adv-test -@hash
|
||||
assert_equal "+@all -@slow +hget -@hash" [dict get [r ACL getuser adv-test] commands]
|
||||
|
||||
# Inverting categories removes existing categories
|
||||
r ACL SETUSER adv-test +@hash
|
||||
assert_equal "+@all -@slow +hget +@hash" [dict get [r ACL getuser adv-test] commands]
|
||||
|
||||
# Make sure categories are case insensitive
|
||||
r ACL SETUSER adv-test -@all +@HASH +@hash +@HaSh
|
||||
assert_equal "-@all +@hash" [dict get [r ACL getuser adv-test] commands]
|
||||
|
||||
# Make sure commands are case insensitive
|
||||
r ACL SETUSER adv-test -@all +HGET +hget +hGeT
|
||||
assert_equal "-@all +hget" [dict get [r ACL getuser adv-test] commands]
|
||||
|
||||
# Arbitrary category additions and removals are handled
|
||||
r ACL SETUSER adv-test -@all +@hash +@slow +@set +@set +@slow +@hash
|
||||
assert_equal "-@all +@set +@slow +@hash" [dict get [r ACL getuser adv-test] commands]
|
||||
|
||||
# Arbitrary command additions and removals are handled
|
||||
r ACL SETUSER adv-test -@all +hget -hset +hset -hget
|
||||
assert_equal "-@all +hset -hget" [dict get [r ACL getuser adv-test] commands]
|
||||
|
||||
# Arbitrary subcommands are compacted
|
||||
r ACL SETUSER adv-test -@all +client|list +client|list +config|get +config +acl|list -acl
|
||||
assert_equal "-@all +client|list +config -acl" [dict get [r ACL getuser adv-test] commands]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user