漫话Redis源码之八十七
2022/2/21 2:26:29
本文主要是介绍漫话Redis源码之八十七,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!
这个文件的函数比较杂,大致看看就行,以第一个为例,就是一下哈希相关的操作,为了密码安全。其余的函数了解用途就行。
/* 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; }
这篇关于漫话Redis源码之八十七的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!
- 2024-11-08阿里云Redis项目实战入门教程
- 2024-11-08阿里云Redis资料:新手入门与初级使用指南
- 2024-11-08阿里云Redis教程:新手入门及实用指南
- 2024-11-07阿里云Redis学习入门:新手必读指南
- 2024-11-07阿里云Redis学习入门:从零开始的操作指南
- 2024-11-07阿里云Redis学习:初学者指南
- 2024-11-06阿里云Redis入门教程:轻松搭建与使用指南
- 2024-11-02Redis项目实战:新手入门教程
- 2024-10-22Redis入门教程:轻松掌握数据存储与操作
- 2024-10-22Redis缓存入门教程:快速掌握Redis缓存基础知识