这个文件的函数比较杂,大致看看就行,以第一个为例,就是一下哈希相关的操作,为了密码安全。其余的函数了解用途就行。
- /* Given an SDS string, returns the SHA256 hex representation as a
- * new SDS string. */
- sds ACLHashPassword(unsigned char *cleartext, size_t len) {
- SHA256_CTX ctx;
- unsigned char hash[SHA256_BLOCK_SIZE];
- char hex[HASH_PASSWORD_LEN];
- char *cset = "0123456789abcdef";
-
- sha256_init(&ctx);
- sha256_update(&ctx,(unsigned char*)cleartext,len);
- sha256_final(&ctx,hash);
-
- for (int j = 0; j < SHA256_BLOCK_SIZE; j++) {
- hex[j*2] = cset[((hash[j]&0xF0)>>4)];
- hex[j*2+1] = cset[(hash[j]&0xF)];
- }
- return sdsnewlen(hex,HASH_PASSWORD_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) {
- if (hashlen != HASH_PASSWORD_LEN) {
- return C_ERR;
- }
-
- /* Password hashes can only be characters that represent
- * hexadecimal values, which are numbers and lowercase
- * characters 'a' through 'f'. */
- for(int i = 0; i < HASH_PASSWORD_LEN; i++) {
- char c = hash[i];
- if ((c < 'a' || c > 'f') && (c < '0' || c > '9')) {
- return C_ERR;
- }
- }
- return C_OK;
- }
-
- /* =============================================================================
- * Low level ACL API
- * ==========================================================================*/
-
- /* Return 1 if the specified string contains spaces or null characters.
- * We do this for usernames and key patterns for simpler rewriting of
- * 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) {
- for (size_t i = 0; i < len; i++) {
- if (isspace(s[i]) || s[i] == 0) return 1;
- }
- return 0;
- }
-
- /* 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. */
- }
-
- /* 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;
- }
-
- /* Method to free list elements from ACL users password/patterns lists. */
- void ACLListFreeSds(void *item) {
- sdsfree(item);
- }
-
- /* Method to duplicate list elements from ACL users password/patterns lists. */
- void *ACLListDupSds(void *item) {
- return sdsdup(item);
- }
-
- /* 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. */
- user *ACLCreateUser(const char *name, size_t namelen) {
- if (raxFind(Users,(unsigned char*)name,namelen) != raxNotFound) return NULL;
- user *u = zmalloc(sizeof(*u));
- u->name = sdsnewlen(name,namelen);
- u->flags = USER_FLAG_DISABLED | server.acl_pubsub_default;
- u->allowed_subcommands = NULL;
- u->passwords = listCreate();
- u->patterns = listCreate();
- u->channels = listCreate();
- listSetMatchMethod(u->passwords,ACLListMatchSds);
- listSetFreeMethod(u->passwords,ACLListFreeSds);
- listSetDupMethod(u->passwords,ACLListDupSds);
- listSetMatchMethod(u->patterns,ACLListMatchSds);
- listSetFreeMethod(u->patterns,ACLListFreeSds);
- listSetDupMethod(u->patterns,ACLListDupSds);
- listSetMatchMethod(u->channels,ACLListMatchSds);
- listSetFreeMethod(u->channels,ACLListFreeSds);
- listSetDupMethod(u->channels,ACLListDupSds);
- memset(u->allowed_commands,0,sizeof(u->allowed_commands));
- raxInsert(Users,(unsigned char*)name,namelen,u,NULL);
- return u;
- }
-
- /* 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;
- }
- }
-
- /* 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);
- listRelease(u->channels);
- ACLResetSubcommands(u);
- zfree(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. */
- void ACLFreeUserAndKillClients(user *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 connection 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;
- /* We will write replies to this client later, so we can't
- * close it directly even if async. */
- if (c == server.current_client) {
- c->flags |= CLIENT_CLOSE_AFTER_COMMAND;
- } else {
- freeClientAsync(c);
- }
- }
- }
- ACLFreeUser(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) {
- listRelease(dst->passwords);
- listRelease(dst->patterns);
- listRelease(dst->channels);
- dst->passwords = listDup(src->passwords);
- dst->patterns = listDup(src->patterns);
- dst->channels = listDup(src->channels);
- 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) {
- raxFreeWithCallback(users,(void(*)(void*))ACLFreeUserAndKillClients);
- }
-
- /* 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. */
- 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));
- 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 representation, 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;
- return (u->allowed_commands[word] & bit) != 0;
- }
-
- /* 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);
- }
-
- /* Set the specified command bit for the specified user to 'value' (0 or 1).
- * If the bit overflows the user internal representation, no operation
- * 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 ACLSetUserCommandBit(user *u, unsigned long id, int value) {
- uint64_t word, bit;
- if (ACLGetCommandBitCoordinates(id,&word,&bit) == C_ERR) return;
- if (value) {
- u->allowed_commands[word] |= bit;
- } else {
- u->allowed_commands[word] &= ~bit;
- u->flags &= ~USER_FLAG_ALLCOMMANDS;
- }
- }
-
- /* 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);
- if (cmd->flags & CMD_MODULE) continue; /* Ignore modules commands. */
- if (cmd->flags & cflag) {
- ACLSetUserCommandBit(u,cmd->id,value);
- ACLResetSubcommandsForCommand(u,cmd->id);
- }
- }
- dictReleaseIterator(di);
- return C_OK;
- }
-
- /* 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 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 ");
- ACLSetUser(fakeuser,"+@all",-1);
- } else {
- additive = 1;
- rules = sdscat(rules,"-@all ");
- ACLSetUser(fakeuser,"-@all",-1);
- }
-
- /* Attempt to find a good approximation for categories and commands
- * based on the current bits used, by looping over the category list
- * and applying the best fit each time. Often a set of categories 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. A temp user is maintained to keep track of categories
- * already applied. */
- user tu = {0};
- user *tempuser = &tu;
-
- /* Keep track of the categories that have been applied, to prevent
- * applying them twice. */
- char applied[sizeof(ACLCommandCategories)/sizeof(ACLCommandCategories[0])];
- memset(applied, 0, sizeof(applied));
-
- memcpy(tempuser->allowed_commands,
- u->allowed_commands,
- sizeof(u->allowed_commands));
- while (1) {
- int best = -1;
- unsigned long mindiff = INT_MAX, maxsame = 0;
- for (int j = 0; ACLCommandCategories[j].flag != 0; j++) {
- if (applied[j]) continue;
-
- unsigned long on, off, diff, same;
- ACLCountCategoryBitsForUser(tempuser,&on,&off,ACLCommandCategories[j].name);
- /* Check if the current category is the best this loop:
- * * It has more commands in common with the user than commands
- * that are different.
- * AND EITHER
- * * It has the fewest number of differences
- * than the best match we have found so far.
- * * OR it matches the fewest number of differences
- * that we've seen but it has more in common. */
- diff = additive ? off : on;
- same = additive ? on : off;
- if (same > diff &&
- ((diff < mindiff) || (diff == mindiff && same > maxsame)))
- {
- best = j;
- mindiff = diff;
- maxsame = same;
- }
- }
-
- /* We didn't find a match */
- if (best == -1) break;
-
- sds op = sdsnewlen(additive ? "+@" : "-@", 2);
- op = sdscat(op,ACLCommandCategories[best].name);
- ACLSetUser(fakeuser,op,-1);
-
- sds invop = sdsnewlen(additive ? "-@" : "+@", 2);
- invop = sdscat(invop,ACLCommandCategories[best].name);
- ACLSetUser(tempuser,invop,-1);
-
- rules = sdscatsds(rules,op);
- rules = sdscatlen(rules," ",1);
- sdsfree(op);
- sdsfree(invop);
-
- applied[best] = 1;
- }
-
- /* Fix the final ACLs with single commands differences. */
- dictIterator *di = dictGetIterator(server.orig_commands);
- dictEntry *de;
- while ((de = dictNext(di)) != NULL) {
- struct redisCommand *cmd = dictGetVal(de);
- int userbit = ACLGetUserCommandBit(u,cmd->id);
- int fakebit = ACLGetUserCommandBit(fakeuser,cmd->id);
- if (userbit != fakebit) {
- rules = sdscatlen(rules, userbit ? "+" : "-", 1);
- rules = sdscat(rules,cmd->name);
- rules = sdscatlen(rules," ",1);
- ACLSetUserCommandBit(fakeuser,cmd->id,userbit);
- }
-
- /* 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);
- }
- }
- }
- dictReleaseIterator(di);
-
- /* Trim the final useless space. */
- sdsrange(rules,0,-2);
-
- /* 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. */
- if (memcmp(fakeuser->allowed_commands,
- u->allowed_commands,
- 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()");
- }
- return rules;
- }
-
- /* 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++) {
- /* Skip the allcommands, allkeys and allchannels flags because they'll
- * be emitted later as +@all, ~* and &*. */
- if (ACLUserFlags[j].flag == USER_FLAG_ALLKEYS ||
- ACLUserFlags[j].flag == USER_FLAG_ALLCHANNELS ||
- ACLUserFlags[j].flag == USER_FLAG_ALLCOMMANDS) continue;
- 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);
- }
- }
-
- /* Pub/sub channel patterns. */
- if (u->flags & USER_FLAG_ALLCHANNELS) {
- res = sdscatlen(res,"&* ",3);
- } else {
- res = sdscatlen(res,"resetchannels ",14);
- listRewind(u->channels,&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;
- }