redis之相思而不得见-细粒度的访问控制列表ACL

# 一、简介
对于早期的redis,在安全访问控制方面非常的弱,使用默认配置的话,能访问redis服务的任何人都能连接上服务,进行数据的操作,简直就是在裸奔一样;
在redis2.2之前的版本最多也只能通过requirepass配置一个密码,任何知道密码的人也可以连接上redis进行删库跑路 😆 ,并且redis6之前的传输都是基于TCP明文传输的,直接抓个包就看到密码了,无密码可言;
为了防止一些人对redis操作非常危险的命令,比如删库命令flushall等,在redis2.2.0开始可以通过rename-command配置将危险的命令重命名为众人不知道的命令,减少危险操作;
而到了redis6增加了TLS加密传输,并且引入了ACL访问控制列表,非常细粒度的控制命令执行权限等,这样就不能想干啥就干啥,正如题目那样对于数据是相思而不得见,哈哈哈。
请添加图片描述

acl访问控制是基于帐号来的,对于不同的帐号可以设置不同的密码,不同的权限。帐号可以被创建、删除、禁用、启用,设置密码,清除密码等。而对于redis访问权限又分为命令的执行权限以及数据key的访问权限
命令的执行权限细分为

  • 具体某个命令或其子命令的权限
  • 某一类命令的权限

数据key的权限细分为

  • Pub/Sub channel
  • key

二、ACL 规则简介

2.1 用户级别

2.1.1 启用/禁用用户

启用或者禁用一个user,禁用后,则不能使用此user进行登录,但是已经登录的连接不受影响。
on: 启用某个user
off: 禁用某个user

127.0.0.1:6379> auth user1 111
OK
127.0.0.1:6379> acl setuser user1 off
OK
127.0.0.1:6379> auth user1 111
(error) WRONGPASS invalid username-password pair or user is disabled.
127.0.0.1:6379> acl setuser user1 on
OK
127.0.0.1:6379> auth user1 111
OK

2.1.2 密码配置

2.1.2.1 添加密码

>password
通过>将密码password添加到用户上,而每个用户上都可以有任意多的密码,以链表的形式组织,存储的是密码的hash值。

2.1.2.2 删除密码

<password
从合法的密码列表中删除密码password,如果要删除的密码不存在,则会报错。

2.1.2.3 设置密码的hash

对于acl可以单独配置到acl.conf文件中,而对于密码明文存储的话很危险,所以可以设置密码的hash值进行存储,其中hash使用的256位,只能是小写的16进制,并且必须是64字节。

#hash

2.1.2.4 取消密码的hash

!hash
从有效的密码列表中删除hash的密码。

2.1.2.5 清除所有密码

nopass
清除当前用户的所有密码,并且设置flag,使用任何密码都可以认证通过。

2.1.2.6 重置密码

resetpass
此命令和nopass相比,多了清除flag,后续将没有密码,并且没有flag,不能进行登录认证,必须设置密码或则设置nopass标志。

2.2 命令级别

2.2.1 具体命令

2.2.1.1 授权命令执行

+命令
比如 +GET,此用户将有执行GET命令的权限

+命令|子命令
比如 +config|get将有config的get子命令执行权限,这样可以更细粒度的控制命令的执行,比如只开放某个命令的某个子命令执行权限。

+命令|第一个参数
指定第一个参数为配置的值才能执行此命令,否则此命令不能执行(即无执行权限)。注意这个方式只能使用 + 进行处理,而不能使用 - 来排除某个值
比如 +SELECT|0。

2.2.1.2 禁用命令执行

-命令
禁止使用某个命令,比如-GET,将没有执行GET命令的权限。

-命令|子命令
禁止某个命令的子命令,比如 -config|set, 对于config命令,不能执行set子命令,即只能查看不能修改。

2.2.2 大类命令

redis中对每个命令都做了一个归类,分为read,write,set,sortedset,list,hash,string,bitmap,hyperloglog,geo,stream,pubsub,admin,fast,slow,blocking,dangerous,connection,transaction,scripting这些大类。可以对用户设置某一类的执行权限,而不用一个一个命令的设置,大大方便了配置等。

2.2.2.1 授权大类命令执行

+@大类名
比如 +@hash, 则有了对于hash类的命令的执行(hset,hsetnx,hget,hmset,hmget等)
可以通过 acl的cat子命令进程查看某个大类中具体有哪些命令

127.0.0.1:6379> acl cat hash
 1) "hgetall"
 2) "hdel"
 3) "hkeys"
 4) "hincrbyfloat"
 5) "hrandfield"
 6) "hset"
 7) "hincrby"
 8) "hvals"
 9) "hmget"
10) "hstrlen"
11) "hscan"
12) "hget"
13) "hsetnx"
14) "hlen"
15) "hexists"
16) "hmset"
2.2.2.2 禁止大类命令执行

-@大类名
禁止某个大类中的所有命令的执行权限,比如 -@set,则对于集合相关的命令都不能执行。

2.2.3 所有命令

2.2.3.1 授权所有命令的执行权限

allcommands+@all, 这两个相同的意思授权所有命令都有执行权限,其中的all这个是一个虚拟的大类,表示所有的命令。
隐式的设置了所有的命令,比如后续通过module增加的命令

2.2.3.2 禁止所有命令的执行权限

nocommands-@all, 禁止所有命令的执行权限, 和allcommands,+@all相对。

2.3 数据级别

2.3.1 key

对于key,都是字符串,所有满足正则条件的key,都授予被访问权限,否则就没有访问权限,可以配置多个 即 ~pattern1 ~pattern2 ~pattern3 …。

2.3.1.1 满足正则条件的key

~pattern
满足正则条件的key可以被访问。比如~userid:*, 表示有权限访问userid:开头的任意key。
其中正则支持格式:
?,任意单个字符
*,零个或多个任意字符
[ab],列表中任意某个字符
[^abc],排除列表中的任意字符
[a-z],多个连续的字符可以简写为一个范围,匹配范围里任意一个字符
\转移字符,对以上的元字符进行转移

2.3.1.2 所有的key

~*
allkeys 是 ~*的别名,表示可以访问所有的key。

2.3.1.3 重置key

resetkeys
删除所有的key相关的配置,即所有的key都不能访问。

2.3.2 Pub/Sub channels

channel也和key类似,也是一个名字,字符串,所以也是通过正则表达式配置,满足正则表达式的名字的channel就有访问的权限,否则没有权限访问这些channel。

2.3.2.1 满足正则条件的channel

&pattern
其中pattern和key类似,但是少了一些。
其中正则支持格式:
?,任意单个字符
*,零个或多个任意字符
[ab],列表中任意某个字符
\转移字符,对以上的元字符进行转移

2.3.3.2 所有的channel

&*
allchannels 是&*的别名。有权访问所有的channel。

2.3.3.3 重置channel

resetchannels
如果某些client使用了没有权限的channel, 这些client将被断开连接。

三、命令简介

3.1 规则解析顺序

从左到右的依次解析,多个规则顺序任意,但又不能任意,如果有重叠部分,后面的规则覆盖前面的。
比如要实现规则排除SET命令的所有命令的执行权限
-SET +@all,这样+@all将覆盖-SET,这样的最终结果是所有命令都有执行权限,所以正确配置为 +@all -SET, 对于这种有包含关系的规则时要特别注意。

3.2 创建用户

acl setuser username acl规则
默认创建的用户是禁用的,需要后续使用on进行启用,并且没有命令执行权限,不能访问任何key,可以访问所有channel(channel是新增加的,为了向下兼容,所以默认有这个权限, 可以通过配置进行设置channel的默认行为

127.0.0.1:6379> acl setuser zhangsan
OK
127.0.0.1:6379> acl list
1) "user default on nopass ~* &* +@all"
2) "user zhangsan off &* -@all"

也可以一次性将规则都写完整

127.0.0.1:6379> acl setuser lisi +@all -set -@hash -info -save ~pid:* &* on >123456
OK
127.0.0.1:6379> acl list
1) "user default on nopass ~* &* +@all"
2) "user lisi on #8d969eef6ecad3c29a3a629280e686cf0c3f5d5a86aff3ca12020c923adc6c92 ~pid:* &* +@all -@hash -save -info -set"
3) "user zhangsan off &* -@all"

现在就可以使用新的用户进行登录

127.0.0.1:6379> auth lisi 123456
OK

3.3 删除用户

127.0.0.1:6379> acl deluser zhangsan
(integer) 1
127.0.0.1:6379> acl list
1) "user default on nopass ~* &* +@all"
2) "user lisi on #8d969eef6ecad3c29a3a629280e686cf0c3f5d5a86aff3ca12020c923adc6c92 ~pid:* &* +@all -@hash -save -info -set"
127.0.0.1:6379> 

default用户不能删除
将某个user删除后,使用此user登录的连接client都将被断开连接。

3.4 保存配置

acl save
此命令将配置的acl规则信息保存到单独的配置文件中,所以redis.conf中必须配置aclfile。

3.5 加载配置

acl load
此命令将从单独的acl.conf(通过配置获取的)文件中加载alc规则,因此必须提前配置aclfile文件。

3.6 列出所有用户

acl users
此命令列出当前redis中所有的用户名,包括禁用的。

127.0.0.1:6379> acl users
1) "default"
2) "zhangsan"

3.7 列出所有用户详细信息

acl list
此命令列出所有的用户以及详细的acl规则。

127.0.0.1:6379> acl list
1) "user default on nopass ~* &* +@all"
2) "user zhangsan off &* -@all"

3.8 获取某个用户信息

alc getusr 用户名
通过指定用户名可以获取对应用户的acl规则详细信息,和acl list类似,但是acl list是列出所有的,而且getuser返回结果是一个列表,list返回一个规则字符串。

127.0.0.1:6379> acl getuser zhangsan
 1) "flags"
 2) 1) "off"
    2) "allchannels"
 3) "passwords"
 4) (empty array)
 5) "commands"
 6) "-@all"
 7) "keys"
 8) (empty array)
 9) "channels"
10) 1) "*"

如下是一个简单的对比

子命令参数个数返回用户个数返回形式例子
users0all所有用户名的列表

1) "default"

2) “zhangsan”

getuser111拆分为列表的详细acl访问权限

1) "flags"

2) 1) "off"
2) "allchannels"

3) "passwords"

4) (empty array)

5) "commands"

6) "-@all"

7) "keys"

8) (empty array)

9) "channels"

10) 1) “*”

list0all所有用户的acl规则列表

1) "user default on nopass ~* &* +@all"

2) “user zhangsan off &* -@all”

3.9 获取acl日志

acl log [条数/reset]
此命令获取在redis运行过程中那些因为acl规则没有权限导致执行失败的日志,如果没有指定条数则返回10条的日志,否则返回对应条数(如果有10条,而要返回20条,则只能返回10条);或则设置reset参数,将日志清空。

127.0.0.1:6379> acl log
1)  1) "count"
    2) (integer) 2
    3) "reason"
    4) "command"
    5) "context"
    6) "toplevel"
    7) "object"
    8) "acl"
    9) "username"
   10) "zhangsan"
   11) "age-seconds"
   12) "58.259999999999998"
   13) "client-info"
   14) "id=3 addr=127.0.0.1:43246 laddr=127.0.0.1:6379 fd=8 name= age=1956 idle=0 flags=N db=0 sub=0 psub=0 multi=-1 qbuf=22 qbuf-free=40932 argv-mem=6 obl=0 oll=0 omem=0 tot-mem=61462 events=r cmd=acl user=zhangsan redir=-1"

3.10 获取当前登录用户

acl whoami
此命令获取当前认证的用户是谁。

127.0.0.1:6379> acl whoami
"default"

3.11 生成指定位数的密码

acl genpass [位数]
此命令生成指定位数的密码,位数最大为4096,如果未指定,默认256。

127.0.0.1:6379> acl genpass
"5b2b83056e5fd4253a10351207ee214176b181103734f48ed9d12525983ccb21"
127.0.0.1:6379> acl genpass 10
"528"

3.12 帮助信息

acl help
此命令将详细的列出命令的使用说明。

127.0.0.1:6379> acl help
 1) ACL <subcommand> [<arg> [value] [opt] ...]. Subcommands are:
 2) CAT [<category>]
 3)     List all commands that belong to <category>, or all command categories
 4)     when no category is specified.
 5) DELUSER <username> [<username> ...]
 6)     Delete a list of users.
 7) GETUSER <username>
 8)     Get the user's details.
 9) GENPASS [<bits>]
10)     Generate a secure 256-bit user password. The optional `bits` argument can
11)     be used to specify a different size.
12) LIST
13)     Show users details in config file format.
14) LOAD
15)     Reload users from the ACL file.
16) LOG [<count> | RESET]
17)     Show the ACL log entries.
18) SAVE
19)     Save the current config to the ACL file.
20) SETUSER <username> <attribute> [<attribute> ...]
21)     Create or modify a user with the specified attributes.
22) USERS
23)     List all the registered usernames.
24) WHOAMI
25)     Return the current connection username.
26) HELP
27)     Prints this help.
127.0.0.1:6379> 

四、代码实现

4.1 数据结构描述

4.1.1 用户结构

#define USER_COMMAND_BITS_COUNT 1024
typedef struct {
    sds name;   //用户名
    uint64_t flags; //一些标志,比如启用、禁用等

	/*
	* 可执行的命令的位图
	* 每个命令分配了一个唯一id,id对应的bit位为1的表示有权限执行,否则没权限执行
	*/
    uint64_t allowed_commands[USER_COMMAND_BITS_COUNT/64];

    /*
	* 可执行的子命令的指针数组
	* 每个命令的id对应数组下标,其子命令为一个链表
	*/
    sds **allowed_subcommands;
    
    list *passwords; //密码链表,存储的hash
    list *patterns;  //key的规则链表;当规则为allkeys时,此链表为空,其他时候此链表为空,则没有key的访问权限
    list *channels;  //channel规则的链表;当规则时allchannels时,此链表为空,其他时候此链表为空,则没有channel的访问权限
} user;

其中位图最大为1024,所以最大命令个数为1024个,超过了则不能处理

4.1.2 client结构

每一个连接到redis服务器上的client中都有一个user指针和某个user信息进行关联。

typedef struct client {
...
 user *user;  /* User associated with this connection. 
 If the user is set to NULL the connection can do anything (admin). */
...
};

4.2 整体组织结构

使用了rax结构进行存储,能根据用户名快速的定位对应的用户对象。
其中rax见https://blog.csdn.net/happytree001/article/details/120165347

比如有Joanne,Kenyon,Diane,Barry,Bridgette这几个用户
请添加图片描述

4.3 redis启动时规则加载

4.3.1 创建default用户

int main(int argc, char **argv) {
...
	ACLInit(); 
...
}
//全局变量
rax *Users; 
user *DefaultUser; 
list *UsersToLoad;  
list *ACLLog;    
   
/* Initialization of the ACL subsystem. */
void ACLInit(void) {
    Users = raxNew();
    UsersToLoad = listCreate();
    ACLLog = listCreate();
    ACLInitDefaultUser();
}
/* Initialize the default user, that will always exist for all the process
 * lifetime. */
void ACLInitDefaultUser(void) {
    DefaultUser = ACLCreateUser("default",7);
    ACLSetUser(DefaultUser,"+@all",-1);
    ACLSetUser(DefaultUser,"~*",-1);
    ACLSetUser(DefaultUser,"&*",-1);
    ACLSetUser(DefaultUser,"on",-1);
    ACLSetUser(DefaultUser,"nopass",-1);
}

4.3.2 解析配置文件(redis.conf)

因为规则可以通过单独的acl.conf文件进行存储,也可以通过redis.conf文件中直接存储,但是这两种方式不能共存。如果redis.conf中有acl规则,则在进行合法性校验后,将合法的规则都放入到UsersToLoad链表中。
redis.conf中的规则和单独存放acl.conf文件内容规则都是一样的。

4.3.2.1 将rule加入到UsersToLoad链表
# redis.conf
...
user worker +@list +@connection ~jobs:* on >ffa9203c493aa99
...
int main(int argc, char **argv) {
...
loadServerConfig(server.configfile, config_from_stdin, options);
...
}
void loadServerConfig(char *filename, char config_from_stdin, char *options) {
   ...
   loadServerConfigFromString(config);
    ...
}
void loadServerConfigFromString(char *config) {
...
 for (i = 0; i < totlines; i++) {
 	...
 	 else if (!strcasecmp(argv[0],"user") && argc >= 2) {
            int argc_err;
            if (ACLAppendUserForLoading(argv,argc,&argc_err) == C_ERR) {
                ...
                goto loaderr;
            }
        } 
 	...
 }
...
}
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;
    }

    /* Try to apply the user rules in a fake user to see if they
     * are actually valid. */
    user *fakeuser = ACLCreateUnlinkedUser();

    for (int j = 2; j < argc; j++) {
        if (ACLSetUser(fakeuser,argv[j],sdslen(argv[j])) == C_ERR) {
            if (errno != ENOENT) {
                ACLFreeUser(fakeuser);
                if (argc_err) *argc_err = j;
                return C_ERR;
            }
        }
    }

    /* 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);
    ACLFreeUser(fakeuser);
    return C_OK;
}

4.3.3 从链表中解析规则

第一步首先会判断链表和acl文件是否同时存在,同时存在则出错,启动失败。

int main(int argc, char **argv) {
	...
	 ACLLoadUsersAtStartup();
	...
}

首先判断单独的acl文件和redis.conf单独的acl规则是否同时存在,是则出错,启动失败,进程退出。

void ACLLoadUsersAtStartup(void) {
    if (server.acl_filename[0] != '\0' && listLength(UsersToLoad) != 0) {
        serverLog(LL_WARNING,
            "Configuring Redis with users defined in redis.conf and at "
            "the same setting an ACL file path is invalid. This setup "
            "is very likely to lead to configuration errors and security "
            "holes, please define either an ACL file or declare users "
            "directly in your redis.conf, but not both.");
        exit(1);
    }
	....
}

尝试从redis.conf文件中加载acl规则。

int ACLLoadConfiguredUsers(void) {
    listIter li;
    listNode *ln;
    listRewind(UsersToLoad,&li);
    while ((ln = listNext(&li)) != NULL) {
        sds *aclrules = listNodeValue(ln);
        sds username = aclrules[0];

        if (ACLStringHasSpaces(aclrules[0],sdslen(aclrules[0]))) {
            serverLog(LL_WARNING,"Spaces not allowed in ACL usernames");
            return C_ERR;
        }

        user *u = ACLCreateUser(username,sdslen(username));
        if (!u) {
            u = ACLGetUserByName(username,sdslen(username));
            serverAssert(u != NULL);
            ACLSetUser(u,"reset",-1);
        }

        /* Load every rule defined for this user. */
        for (int j = 1; aclrules[j]; j++) {
            if (ACLSetUser(u,aclrules[j],sdslen(aclrules[j])) != C_OK) {
                const char *errmsg = ACLSetUserStringError();
                serverLog(LL_WARNING,"Error loading ACL rule '%s' for "
                                     "the user named '%s': %s",
                          aclrules[j],aclrules[0],errmsg);
                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;
}

4.3.4 从acl.conf单独文件中加载

如果是配置了aclfile指定单独的acl文件,并且redis.conf文件中没有单独的acl规则。则从单独的acl文件中进行解析加载。具体的加载过程在后续的load命令解析时再讲解。

if (server.acl_filename[0] != '\0') {
    sds errors = ACLLoadFromFile(server.acl_filename);
    if (errors) {
        serverLog(LL_WARNING,
                  "Aborting Redis startup because of ACL errors: %s", errors);
        sdsfree(errors);
        exit(1);
    }
}

4.4 命令解析

4.4.1 setuser

void aclCommand(client *c) {
 	char *sub = c->argv[1]->ptr;
    if (!strcasecmp(sub,"setuser") && c->argc >= 3) {
    ...
    }
    ...
}
4.4.1.1 检查用户名合法性

对于用户名来说,不能有空格和null字符。

	 sds username = c->argv[2]->ptr;
    /* Check username validity. */
      if (ACLStringHasSpaces(username,sdslen(username))) {
          addReplyErrorFormat(c,
              "Usernames can't contain spaces or null characters");
          return;
      }
//检查字符串中是否包含空格或则null字符
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;
}
4.4.1.2 创建临时用户

这里创建临时用户,后续的解析修改都是在这个临时对象上进行,不影响现有正常数据,如果其中某个参数解析错误等,直接释放临时对象,然后返回即可,并且因为redis没有回滚操作,这样也保证了每条命令执行的原子性。
如果是已经存在的对象,则将对象的数据复制到这个临时对象中,后续修改都在这个临时用户上,等完全修改成功后,再将临时对象的数据复制回原有对象。

...
user *tempu = ACLCreateUnlinkedUser();
user *u = ACLGetUserByName(username,sdslen(username));
if (u) ACLCopyUser(tempu, u);
...

创建一个临时用户,此函数是一个死循环,直到创建成功,而和普通的创建用户对象的不同之处只是将对象从rax树上删除,即没有和任何结构进行关联。

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;
    }
}

创建用户最终调用的是ACLCreateUser函数,创建对象后,直接插入到rax树上,后续可以根据用户名快速的检索到对应的对象。如果对象已经存在,则直接返回null。

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;
}
4.4.1.3 混淆参数

为了不能从日志信息中泄漏敏感信息,所以将参数进行编辑替换。(具体的替换逻辑,后续再详细分析

 /* Initially redact all of the arguments to not leak any information
* about the user. */
for (int j = 2; j < c->argc; j++) {
    redactClientCommandArgument(c, j);
}
void redactClientCommandArgument(client *c, int argc) {
    retainOriginalCommandVector(c);
    decrRefCount(c->argv[argc]);
    c->original_argv[argc] = shared.redacted;
}
static void retainOriginalCommandVector(client *c) {
    /* We already rewrote this command, so don't rewrite it again */
    if (c->original_argv) return;
    c->original_argc = c->argc;
    c->original_argv = zmalloc(sizeof(robj*)*(c->argc));
    for (int j = 0; j < c->argc; j++) {
        c->original_argv[j] = c->argv[j];
        incrRefCount(c->argv[j]);
    }
}
4.4.1.4 参数解析

逐一的解析参数,将参数应用到临时对象上,这里相当于一个小白鼠对象,去尝试是否有问题,当一切结束,都没有问题后,真正的对象才会去修改。

	for (int j = 3; j < c->argc; j++) {
       if (ACLSetUser(tempu,c->argv[j]->ptr,sdslen(c->argv[j]->ptr)) != C_OK) {
            const char *errmsg = ACLSetUserStringError();
            addReplyErrorFormat(c,
                "Error in ACL SETUSER modifier '%s': %s",
                (char*)c->argv[j]->ptr, errmsg);

            ACLFreeUser(tempu);
            return;
        }
    }

ACLSetUser函数是规则解析的核心函数,解析规则,设置对应的flags等。

int ACLSetUser(user *u, const char *op, ssize_t oplen) {
    if (oplen == -1) oplen = strlen(op);
    if (oplen == 0) return C_OK; /* Empty string is a no-operation. */
    if (!strcasecmp(op,"on")) {
        u->flags |= USER_FLAG_ENABLED;
        u->flags &= ~USER_FLAG_DISABLED;
    } else if (!strcasecmp(op,"off")) {
        u->flags |= USER_FLAG_DISABLED;
        u->flags &= ~USER_FLAG_ENABLED;
    } else if (!strcasecmp(op,"skip-sanitize-payload")) {
        u->flags |= USER_FLAG_SANITIZE_PAYLOAD_SKIP;
        u->flags &= ~USER_FLAG_SANITIZE_PAYLOAD;
    } else if (!strcasecmp(op,"sanitize-payload")) {
        u->flags &= ~USER_FLAG_SANITIZE_PAYLOAD_SKIP;
        u->flags |= USER_FLAG_SANITIZE_PAYLOAD;
    } else if (!strcasecmp(op,"allkeys") ||
               !strcasecmp(op,"~*"))
    {
        u->flags |= USER_FLAG_ALLKEYS;
        listEmpty(u->patterns);
    } else if (!strcasecmp(op,"resetkeys")) {
        u->flags &= ~USER_FLAG_ALLKEYS;
        listEmpty(u->patterns);
    } else if (!strcasecmp(op,"allchannels") ||
               !strcasecmp(op,"&*"))
    {
        u->flags |= USER_FLAG_ALLCHANNELS;
        listEmpty(u->channels);
    } else if (!strcasecmp(op,"resetchannels")) {
        u->flags &= ~USER_FLAG_ALLCHANNELS;
        listEmpty(u->channels);
    } else if (!strcasecmp(op,"allcommands") ||
               !strcasecmp(op,"+@all"))
    {
        memset(u->allowed_commands,255,sizeof(u->allowed_commands));
        u->flags |= USER_FLAG_ALLCOMMANDS;
        ACLResetSubcommands(u);
    } else if (!strcasecmp(op,"nocommands") ||
               !strcasecmp(op,"-@all"))
    {
        memset(u->allowed_commands,0,sizeof(u->allowed_commands));
        u->flags &= ~USER_FLAG_ALLCOMMANDS;
        ACLResetSubcommands(u);
    } else if (!strcasecmp(op,"nopass")) {
        u->flags |= USER_FLAG_NOPASS;
        listEmpty(u->passwords);
    } else if (!strcasecmp(op,"resetpass")) {
        u->flags &= ~USER_FLAG_NOPASS;
        listEmpty(u->passwords);
    } else if (op[0] == '>' || op[0] == '#') {
        sds newpass;
        if (op[0] == '>') {
            newpass = ACLHashPassword((unsigned char*)op+1,oplen-1);
        } else {
            if (ACLCheckPasswordHash((unsigned char*)op+1,oplen-1) == C_ERR) {
                errno = EBADMSG;
                return C_ERR;
            }
            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);
        else
            sdsfree(newpass);
        u->flags &= ~USER_FLAG_NOPASS;
    } else if (op[0] == '<' || op[0] == '!') {
        sds delpass;
        if (op[0] == '<') {
            delpass = ACLHashPassword((unsigned char*)op+1,oplen-1);
        } else {
            if (ACLCheckPasswordHash((unsigned char*)op+1,oplen-1) == C_ERR) {
                errno = EBADMSG;
                return C_ERR;
            }
            delpass = sdsnewlen(op+1,oplen-1);
        }
        listNode *ln = listSearchKey(u->passwords,delpass);
        sdsfree(delpass);
        if (ln) {
            listDelNode(u->passwords,ln);
        } else {
            errno = ENODEV;
            return C_ERR;
        }
    } else if (op[0] == '~') {
        if (u->flags & USER_FLAG_ALLKEYS) {
            errno = EEXIST;
            return C_ERR;
        }
        if (ACLStringHasSpaces(op+1,oplen-1)) {
            errno = EINVAL;
            return C_ERR;
        }
        sds newpat = sdsnewlen(op+1,oplen-1);
        listNode *ln = listSearchKey(u->patterns,newpat);
        /* Avoid re-adding the same key pattern multiple times. */
        if (ln == NULL)
            listAddNodeTail(u->patterns,newpat);
        else
            sdsfree(newpat);
        u->flags &= ~USER_FLAG_ALLKEYS;
    } else if (op[0] == '&') {
        if (u->flags & USER_FLAG_ALLCHANNELS) {
            errno = EISDIR;
            return C_ERR;
        }
        if (ACLStringHasSpaces(op+1,oplen-1)) {
            errno = EINVAL;
            return C_ERR;
        }
        sds newpat = sdsnewlen(op+1,oplen-1);
        listNode *ln = listSearchKey(u->channels,newpat);
        /* Avoid re-adding the same channel pattern multiple times. */
        if (ln == NULL)
            listAddNodeTail(u->channels,newpat);
        else
            sdsfree(newpat);
        u->flags &= ~USER_FLAG_ALLCHANNELS;
    } else if (op[0] == '+' && op[1] != '@') {
        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) {
                zfree(copy);
                errno = ENOENT;
                return C_ERR;
            }

            /* 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;
            }

            unsigned long id = ACLGetCommandID(copy);
            /* Add the subcommand to the list of valid ones, if the command is not set. */
            if (ACLGetUserCommandBit(u,id) == 0) {
                ACLAddAllowedSubcommand(u,id,sub);
            }

            zfree(copy);
        }
    } 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);
        ACLResetSubcommandsForCommand(u,id);
    } 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;
        }
    } else if (!strcasecmp(op,"reset")) {
        serverAssert(ACLSetUser(u,"resetpass",-1) == C_OK);
        serverAssert(ACLSetUser(u,"resetkeys",-1) == C_OK);
        serverAssert(ACLSetUser(u,"resetchannels",-1) == C_OK);
        if (server.acl_pubsub_default & USER_FLAG_ALLCHANNELS)
            serverAssert(ACLSetUser(u,"allchannels",-1) == C_OK);
        serverAssert(ACLSetUser(u,"off",-1) == C_OK);
        serverAssert(ACLSetUser(u,"sanitize-payload",-1) == C_OK);
        serverAssert(ACLSetUser(u,"-@all",-1) == C_OK);
    } else {
        errno = EINVAL;
        return C_ERR;
    }
    return C_OK;
}
4.4.1.5 关闭无权限操作channel的client

被修改的user的channel权限有修改时,如果没有访问权限了,将会关闭当前所有使用此用户登录的连接。

/* Existingpub/sub clients authenticated with the user may need to be
  * disconnected if (some of) their channel permissions were revoked. */
 if (u && !(tempu->flags & USER_FLAG_ALLCHANNELS))
     ACLKillPubsubClientsIfNeeded(u,tempu->channels);
4.4.1.6 创建用户

如果用户不存在,则需要创建用户对象,并且插入到rax树上。

 if (!u) u = ACLCreateUser(username,sdslen(username));
 serverAssert(u != NULL);
4.4.1.7临时对象覆盖已有对象

到这里说明解析没有问题,修改都在临时对象中,现在将应用修改,将临时对象覆写已有对象,并且释放临时对象。

ACLCopyUser(u, tempu);
ACLFreeUser(tempu);
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]);
                }
            }
        }
    }
}
4.4.1.8 响应ok
 addReply(c,shared.ok);

4.4.2 deluser

else if (!strcasecmp(sub,"deluser") && c->argc >= 3) {

可以批量删除用户, acl deluser user1 user2 user3 …

4.4.2.1 检查删除用户中是否有default

default用户不能被删除,所以提前进行检查;而对于删除不存在的用户,也不会报错。

 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;
      }
  }
4.4.2.2 循环删除用户并关闭连接

删除用户的同时,将会关闭所有使用此用户登录的连接。

int deleted = 0;
 ...
for (int j = 2; j < c->argc; j++) {
 	 sds username = c->argv[j]->ptr;
     user *u;
     if (raxRemove(Users,(unsigned char*)username,
                   sdslen(username),
                   (void**)&u))
     {
         ACLFreeUserAndKillClients(u);
         deleted++;
     }
 }
4.4.2.3 响应实际删除个数
 addReplyLongLong(c,deleted);

4.4.3 getuser

else if (!strcasecmp(sub,"getuser") && c->argc == 3) {
4.4.3.1 通过username获取用户对象
user *u = ACLGetUserByName(c->argv[2]->ptr,sdslen(c->argv[2]->ptr));
 if (u == NULL) {
     addReplyNull(c);
     return;
 }
4.4.3.2 提取flags数据

根据不同的flag获取对应的name。

struct ACLUserFlag {
    const char *name;
    uint64_t flag;
} ACLUserFlags[] = {
    /* Note: the order here dictates the emitted order at ACLDescribeUser */
    {"on", USER_FLAG_ENABLED},
    {"off", USER_FLAG_DISABLED},
    {"allkeys", USER_FLAG_ALLKEYS},
    {"allchannels", USER_FLAG_ALLCHANNELS},
    {"allcommands", USER_FLAG_ALLCOMMANDS},
    {"nopass", USER_FLAG_NOPASS},
    {"skip-sanitize-payload", USER_FLAG_SANITIZE_PAYLOAD_SKIP},
    {"sanitize-payload", USER_FLAG_SANITIZE_PAYLOAD},
    {NULL,0} /* Terminator. */
};
  /* Flags */
 addReplyBulkCString(c,"flags");
   void *deflen = addReplyDeferredLen(c);
   int numflags = 0;
   for (int j = 0; ACLUserFlags[j].flag; j++) {
       if (u->flags & ACLUserFlags[j].flag) {
           addReplyBulkCString(c,ACLUserFlags[j].name);
           numflags++;
       }
   }
   setDeferredSetLen(c,deflen,numflags);
4.4.3.3 提取密码数据

从链表中获取数据

/* 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));
   }
4.4.3.4 获取命令规则描述
/* Commands*/
addReplyBulkCString(c,"commands");
sds cmddescr = ACLDescribeUserCommandRules(u);
addReplyBulkSds(c,cmddescr);
4.4.3.5 获取keys的规则
 /* 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));
      }
  }
4.4.3.6 获取channel规则
/* Pub/sub patterns */
addReplyBulkCString(c,"channels");
 if (u->flags & USER_FLAG_ALLCHANNELS) {
     addReplyArrayLen(c,1);
     addReplyBulkCBuffer(c,"*",1);
 } else {
     addReplyArrayLen(c,listLength(u->channels));
     listIter li;
     listNode *ln;
     listRewind(u->channels,&li);
     while((ln = listNext(&li))) {
         sds thispat = listNodeValue(ln);
         addReplyBulkCBuffer(c,thispat,sdslen(thispat));
     }
 }

4.4.4 list/users

对于acl users和acl list相比,前者只是列出所有的user名字,而后者还要将用户的详细信息列出,所以redis实现时,在一个分支里处理。

 else if ((!strcasecmp(sub,"list") || !strcasecmp(sub,"users")) &&  c->argc == 2)

遍历rax树,获取所有的user对象, 如果是users命令则返回用户名,否则获取详细的规则描述进行返回。

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);

4.4.5 whoami

返回当前client登录的是哪个用户

else if (!strcasecmp(sub,"whoami") && c->argc == 2) {
     if (c->user != NULL) {
          addReplyBulkCBuffer(c,c->user->name,sdslen(c->user->name));
      } else {
          addReplyNull(c);
      }

4.4.6 save

将alc规则保存到单独的文件中,所以需要提前配置文件名,没有配置则保存失败。

4.4.6.1 检查是否配置文件名
else if (server.acl_filename[0] == '\0' &&
               (!strcasecmp(sub,"load") || !strcasecmp(sub,"save")))
    {
        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;
    } 
4.4.6.2 将acl规则写入文件

和其他写文件的操作一样,都是先写入临时文件中,写完后,再调用rename进行覆盖,这样是安全的覆盖写操作,值得借鉴。

else if (!strcasecmp(sub,"save") && c->argc == 2) {
        if (ACLSaveToFile(server.acl_filename) == C_OK) {
            addReply(c,shared.ok);
        } else {
            addReplyError(c,"There was an error trying to save the ACLs. "
                            "Please check the server logs for more "
                            "information");
        }

遍历rax树,获取所有对象,根据对象获取alc规则,然后写入临时文件,最后覆盖老的文件。

int ACLSaveToFile(const char *filename) {
    sds acl = sdsempty();
    int fd = -1;
    sds tmpfilename = NULL;
    int retval = C_ERR;

    /* Let's generate an SDS string containing the new version of the
     * ACL file. */
    raxIterator ri;
    raxStart(&ri,Users);
    raxSeek(&ri,"^",NULL,0);
    while(raxNext(&ri)) {
        user *u = ri.data;
        /* Return information in the configuration file format. */
        sds user = sdsnew("user ");
        user = sdscatsds(user,u->name);
        user = sdscatlen(user," ",1);
        sds descr = ACLDescribeUser(u);
        user = sdscatsds(user,descr);
        sdsfree(descr);
        acl = sdscatsds(acl,user);
        acl = sdscatlen(acl,"\n",1);
        sdsfree(user);
    }
    raxStop(&ri);

    /* Create a temp file with the new content. */
    tmpfilename = sdsnew(filename);
    tmpfilename = sdscatfmt(tmpfilename,".tmp-%i-%I",
        (int)getpid(),(int)mstime());
    if ((fd = open(tmpfilename,O_WRONLY|O_CREAT,0644)) == -1) {
        ...
        goto cleanup;
    }

    /* Write it. */
    if (write(fd,acl,sdslen(acl)) != (ssize_t)sdslen(acl)) {
      ...
        goto cleanup;
    }
    close(fd); fd = -1;

    /* Let's replace the new file with the old one. */
    if (rename(tmpfilename,filename) == -1) {
        ...
        goto cleanup;
    }
    sdsfree(tmpfilename); tmpfilename = NULL;
    retval = C_OK; /* If we reached this point, everything is fine. */

cleanup:
    if (fd != -1) close(fd);
    if (tmpfilename) unlink(tmpfilename);
    sdsfree(tmpfilename);
    sdsfree(acl);
    return retval;
}

4.4.7 load

从单独的配置文件中加载acl规则。

else if (!strcasecmp(sub,"load") && c->argc == 2) {
        sds errors = ACLLoadFromFile(server.acl_filename);
        if (errors == NULL) {
            addReply(c,shared.ok);
        } else {
            addReplyError(c,errors);
            sdsfree(errors);
        }
    }
4.4.7.1 读取文件

将文件内容读取到内存中来

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();
    while(fgets(buf,sizeof(buf),fp) != NULL)
        acls = sdscat(acls,buf);
    fclose(fp);
4.4.7.2 预处理数据

将读取到内存中的数据,根据’\n’进行拆分成数组形式。

  /* 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);
    sdsfree(acls);
4.4.7.3 创建临时用户

创建一个临时用户,后续所有操作都是基于这个临时用户操作,主要是为了防止acl规则中有错误。将规则应用到临时对象上,如果没有出错才应用到实际对象上。

 /* We need a fake user to validate the rules before making changes
     * to the real user mentioned in the ACL line. */
    user *fakeuser = ACLCreateUnlinkedUser();
4.4.7.4 保存老的rax树和默认用户

因为后面要重建rax树,为了能够在出现错误的时候进行回退,所以将rax树保存。而对于default用户,为了不改变default用户对象引用的指针,这里做了保存。

/* We do all the loading in a fresh instance 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;
    user *old_default_user = DefaultUser;
4.4.7.5 创建rax树以及初始化
  	Users = raxNew();
 	ACLInitDefaultUser();
4.4.7.6 逐行解析规则

逐行的解析规则,将规则应用到临时对象上,如果没有出错,则创建用户,将临时用户的修改复制到创建的新对象上,否则解析下一条规则。

/* Load each line of the file. */
    for (int i = 0; i < totlines; i++) {
        sds *argv;
        int argc;
        int linenum = i+1;
		//去掉开头的空白字符
        lines[i] = sdstrim(lines[i]," \t\r\n");

        /* 跳过空行 */
        if (lines[i][0] == '\0') continue;

        /* 根据空格将拆分成多个参数 */
        argv = sdssplitlen(lines[i],sdslen(lines[i])," ",1,&argc);
        if (argv == NULL) {
            ...
            continue;
        }

        /* Skip this line if the resulting command vector is empty. */
        if (argc == 0) {
            sdsfreesplitres(argv,argc);
            continue;
        }

        /* The line should start with the "user" keyword. */
        if (strcmp(argv[0],"user") || argc < 2) {
           ...
            continue;
        }

        /* Spaces are not allowed in usernames. */
        if (ACLStringHasSpaces(argv[1],sdslen(argv[1]))) {
            ...
            continue;
        }

        //清空临时对象的规则,然后将规则应用到临时对象上
        ACLSetUser(fakeuser,"reset",-1);
        int j;
        for (j = 2; j < argc; j++) {
            argv[j] = sdstrim(argv[j],"\t\r\n");
            if (ACLSetUser(fakeuser,argv[j],sdslen(argv[j])) != C_OK) {
               ...
                continue;
            }
        }

       //如果在应用规则到临时对象时有任何错误,则跳过当前规则,开始解析下一条规则
        if (sdslen(errors) != 0) {
            sdsfreesplitres(argv,argc);
            continue;
        }

       //执行到这里,说明规则没有错误,开始实际应用规则,将创建新对象
       //如果对象存在,则创建失败,直接将对象的规则reset清空
        user *u = ACLCreateUser(argv[1],sdslen(argv[1]));
        if (!u) {
            u = ACLGetUserByName(argv[1],sdslen(argv[1]));
            serverAssert(u != NULL);
            ACLSetUser(u,"reset",-1);
        }

        //将规则应用到用户上
        for (j = 2; j < argc; j++)
            serverAssert(ACLSetUser(u,argv[j],sdslen(argv[j])) == C_OK);

        sdsfreesplitres(argv,argc);
    }
     //解析全部结束,释放临时对象
    ACLFreeUser(fakeuser);
    //释放数据数组
    sdsfreesplitres(lines,totlines);
4.4.7.7 切换rax树

如果整个加载过程中都没有出错,则开始切换rax树。

因为client中的user字段指向老的rax树,现在rax树已经改变,所以将老的rax上对象上所有的client断开链接(除了default用户)。
而对于default账户进行了特殊处理,它的地址没有改变,只是规则被改变,这样使用default账户登录的人也不会立刻被关闭。

	 DefaultUser = old_default_user; /* This pointer must never change. */
 
  /* Check if we found errors and react accordingly. */
    if (sdslen(errors) == 0) {
        /* 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);
        serverAssert(new != NULL);
        ACLCopyUser(DefaultUser,new);
        ACLFreeUser(new);
        raxInsert(Users,(unsigned char*)"default",7,DefaultUser,NULL);
        raxRemove(old_users,(unsigned char*)"default",7,NULL);
        ACLFreeUsersSet(old_users);
        sdsfree(errors);
        return NULL;

通过回调的方式进行关闭

/* 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);
}
4.4.7.8 回退

如果规则解析有错误,则最终会将整个rax树进行回退,什么都没有修改,并且响应错误。

	ACLFreeUsersSet(Users);
	Users = old_users;
	errors = sdscat(errors,"WARNING: ACL errors detected, no change to the previously active ACL rules was performed");
return errors;

4.4.8 cat

输出大类中的所有命令,如果未指定参数,则输出所有的大类对应的命令,否则只输出对应大类的命令。其中大类是根据命令中的flags标志进行区分的。

else if (!strcasecmp(sub,"cat") && c->argc == 2) {
        void *dl = addReplyDeferredLen(c);
        int j;
        for (j = 0; ACLCommandCategories[j].flag != 0; j++)
            addReplyBulkCString(c,ACLCommandCategories[j].name);
        setDeferredArrayLen(c,dl,j);
} else if (!strcasecmp(sub,"cat") && c->argc == 3) {
        uint64_t cflag = ACLGetCommandCategoryFlagByName(c->argv[2]->ptr);
        if (cflag == 0) {
            addReplyErrorFormat(c, "Unknown category '%s'", (char*)c->argv[2]->ptr);
            return;
        }
        int arraylen = 0;
        void *dl = addReplyDeferredLen(c);
        dictIterator *di = dictGetIterator(server.orig_commands);
        dictEntry *de;
        while ((de = dictNext(di)) != NULL) {
            struct redisCommand *cmd = dictGetVal(de);
            if (cmd->flags & CMD_MODULE) continue;
            if (cmd->flags & cflag) {
                addReplyBulkCString(c,cmd->name);
                arraylen++;
            }
        }
        dictReleaseIterator(di);
        setDeferredArrayLen(c,dl,arraylen);
    } 

4.4.9 genpass

生成指定位数的随机密码(十六进制),如果未指定位数则默认256位, 最大4096位

4.4.9.1 获取需要生成密码的位数
#define GENPASS_MAX_BITS 4096
   char pass[GENPASS_MAX_BITS/8*2]; /* Hex representation. */
    long bits = 256; /* By default generate 256 bits passwords. */

    if (c->argc == 3 && getLongFromObjectOrReply(c,c->argv[2],&bits,NULL)
        != C_OK) return;

    if (bits <= 0 || bits > GENPASS_MAX_BITS) {
        addReplyErrorFormat(c,
            "ACL GENPASS argument must be the number of "
            "bits for the output password, a positive number "
            "up to %d",GENPASS_MAX_BITS);
        return;
    }
4.4.9.2 生成随机密码
	long chars = (bits+3)/4; /* Round to number of characters to emit. */
    getRandomHexChars(pass,chars);
    addReplyBulkCBuffer(c,pass,chars);

4.4.10 log

获取因为无权限导致操作失败的日志,日志是以链表的形式存储在内存中的,如果未指定获取条数,则默认获取10条,也可以通过reset参数清空日志。

else if (!strcasecmp(sub,"log") && (c->argc == 2 || c->argc ==3)) {
4.4.10.1 参数获取

如果有三个参数,并且第三个参数是reset,则直接清空日志链表,这里是同步释放对象,如果日志对象非常多,这里可能要阻塞主线程
,否则获取条数参数;如果没有三个参数,则默认获取10条日志。

 long count = 10; /* Number of entries to emit by default. */

  /* Parse the only argument that LOG may have: it could be either
   * the number of entries the user wants to display, or alternatively
   * the "RESET" command in order to flush the old entries. */
  if (c->argc == 3) {
      if (!strcasecmp(c->argv[2]->ptr,"reset")) {
          listSetFreeMethod(ACLLog,ACLFreeLogEntry);
          listEmpty(ACLLog);
          listSetFreeMethod(ACLLog,NULL);
          addReply(c,shared.ok);
          return;
      } else if (getLongFromObjectOrReply(c,c->argv[2],&count,NULL)
                 != C_OK)
      {
          return;
      }
      if (count < 0) count = 0;
  }

 /* Fix the count according to the number of entries we got. */
if ((size_t)count > listLength(ACLLog))
    count = listLength(ACLLog);
4.4.10.2 获取日志数据并返回
addReplyArrayLen(c,count);
listIter li;
listNode *ln;
listRewind(ACLLog,&li);
mstime_t now = mstime();
while (count-- && (ln = listNext(&li)) != NULL) {
    ACLLogEntry *le = listNodeValue(ln);
    addReplyMapLen(c,7);
    addReplyBulkCString(c,"count");
    addReplyLongLong(c,le->count);

    addReplyBulkCString(c,"reason");
    char *reasonstr;
    switch(le->reason) {
        case ACL_DENIED_CMD: reasonstr="command"; break;
        case ACL_DENIED_KEY: reasonstr="key"; break;
        case ACL_DENIED_CHANNEL: reasonstr="channel"; break;
        case ACL_DENIED_AUTH: reasonstr="auth"; break;
        default: reasonstr="unknown";
    }
    addReplyBulkCString(c,reasonstr);

    addReplyBulkCString(c,"context");
    char *ctxstr;
    switch(le->context) {
        case ACL_LOG_CTX_TOPLEVEL: ctxstr="toplevel"; break;
        case ACL_LOG_CTX_MULTI: ctxstr="multi"; break;
        case ACL_LOG_CTX_LUA: ctxstr="lua"; break;
        default: ctxstr="unknown";
    }
    addReplyBulkCString(c,ctxstr);

    addReplyBulkCString(c,"object");
    addReplyBulkCBuffer(c,le->object,sdslen(le->object));
    addReplyBulkCString(c,"username");
    addReplyBulkCBuffer(c,le->username,sdslen(le->username));
    addReplyBulkCString(c,"age-seconds");
    double age = (double)(now - le->ctime)/1000;
    addReplyDouble(c,age);
    addReplyBulkCString(c,"client-info");
    addReplyBulkCBuffer(c,le->cinfo,sdslen(le->cinfo));
}

五、认证过程

5.1 第一次连接上redis是什么情况?

redis-server在初始化完成后,将注册acceptTcpHandler函数来处理client的连接。

void acceptTcpHandler(aeEventLoop *el, int fd, void *privdata, int mask) {
   ...
    while(max--) {
        cfd = anetTcpAccept(server.neterr, fd, cip, sizeof(cip), &cport);
        ...
        acceptCommonHandler(connCreateAcceptedSocket(cfd),0,cip);
    }
}
static void acceptCommonHandler(connection *conn, int flags, char *ip) {
    client *c;
  
   ...
   
    /* Create connection and client */
    if ((c = createClient(conn)) == NULL) {
        serverLog(LL_WARNING,
            "Error registering fd event for the new client: %s (conn: %s)",
            connGetLastError(conn),
            connGetInfo(conn, conninfo, sizeof(conninfo)));
        connClose(conn); /* May be already closed, just ignore errors */
        return;
    }

    /* Last chance to keep flags */
    c->flags |= flags;
	...
}
client *createClient(connection *conn) {
    client *c = zmalloc(sizeof(client));
     ...
    clientSetDefaultAuth(c);
  	...
    return c;
}

当用户第一次连接server时,服务器将给当前client设置成default账户

== 如果default用户没有被禁用,并且default未配置密码,则此客户端后续不需要进行登录认证,直接就算认证通过了==

static void clientSetDefaultAuth(client *c) {
    /* If the default user does not require authentication, the user is
     * directly authenticated. */
    c->user = DefaultUser;
    c->authenticated = (c->user->flags & USER_FLAG_NOPASS) &&
                       !(c->user->flags & USER_FLAG_DISABLED);
}

5.2 使用auth命令登录后

对于刚建立连接的client来说,可以通过auth命令来切换用户,或者进行default账户的认证(如果default配置了密码requirepass 密码

而此auth密码为了向下兼容,可以只需要两个参数,则用户名默认为default。

5.2.1 获取用户名和密码

void authCommand(client *c) {
    /* Only two or three argument forms are allowed. */
    if (c->argc > 3) {
        addReplyErrorObject(c,shared.syntaxerr);
        return;
    }
    /* Always redact the second argument */
    redactClientCommandArgument(c, 1);

    /* Handle the two different forms here. The form with two arguments
     * will just use "default" as username. */
    robj *username, *password;
    if (c->argc == 2) {
        /* Mimic the old behavior of giving an error for the two commands
         * from if no password is configured. */
        if (DefaultUser->flags & USER_FLAG_NOPASS) {
            addReplyError(c,"AUTH <password> called without any password "
                            "configured for the default user. Are you sure "
                            "your configuration is correct?");
            return;
        }

        username = shared.default_username; 
        password = c->argv[1];
    } else {
        username = c->argv[1];
        password = c->argv[2];
        redactClientCommandArgument(c, 2);
    }

    if (ACLAuthenticateUser(c,username,password) == C_OK) {
        addReply(c,shared.ok);
    } else {
        addReplyError(c,"-WRONGPASS invalid username-password pair or user is disabled.");
    }
}

主要的处理逻辑在ACLAuthenticateUser函数中处理

int ACLAuthenticateUser(client *c, robj *username, robj *password) {
    if (ACLCheckUserCredentials(username,password) == C_OK) {
        c->authenticated = 1;
        c->user = ACLGetUserByName(username->ptr,sdslen(username->ptr));
        moduleNotifyUserChanged(c);
        return C_OK;
    } else {
        addACLLogEntry(c,ACL_DENIED_AUTH,0,username->ptr);
        return C_ERR;
    }
}

5.2.2 根据用户名从rax上获取对象

如果对象不存在,则直接出错返回。

user *u = ACLGetUserByName(username->ptr,sdslen(username->ptr));
if (u == NULL) {
    errno = ENOENT;
    return C_ERR;
}

5.2.3 检测用户是否启用

如果用户被禁用的,则直接出错返回。

/* Disabled users can't login. */
if (u->flags & USER_FLAG_DISABLED) {
    errno = EINVAL;
    return C_ERR;
}

5.2.4 检测用户是否需要密码

如果用户不需要密码,则任何密码都通过,所以直接返回成功。

 /* 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;

5.2.5 检测密码是否正确

从密码列表中逐一进行比对,如果找到密码则返回成功,否则返回认证失败。

/* Check all the user passwords for at least one to match. */
    listIter li;
    listNode *ln;
    listRewind(u->passwords,&li);
    sds hashed = ACLHashPassword(password->ptr,sdslen(password->ptr));
    while((ln = listNext(&li))) {
        sds thispass = listNodeValue(ln);
        if (!time_independent_strcmp(hashed, thispass)) {
            sdsfree(hashed);
            return C_OK;
        }
    }
    sdsfree(hashed);

    /* If we reached this point, no password matched. */
    errno = EINVAL;
    return C_ERR;

而对于密码的比对,使用了一个特定的发放,每次都比较一样的长度(512字节),不管密码实际长度,这样就不容易根据执行时间进行猜测密码。这个可以借鉴到以后开发中如果有安全考虑的地方

#define CONFIG_AUTHPASS_MAX_LEN 512
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. */
}

当认证成功后,client中的user指向rax树上的对象。

5.3 如何查看是否有权限

当client向服务器发送请求时,server在执行请求之前会进行权限的检测。

5.3.1 查看当前用户是否是认证过的

int processCommand(client *c) {
...
 if (authRequired(c)) {
        /* AUTH and HELLO and no auth commands are valid even in
         * non-authenticated state. */
        if (!(c->cmd->flags & CMD_NO_AUTH)) {
            rejectCommand(c,shared.noautherr);
            return C_OK;
        }
    }
...
}

如果是default用户,并且未设置密码,没有被禁用,则认证通过,否则通过auth命令认证通过的,则认证通过,

其他情况都是认证未通过的,没有权限,直接返回错误,请求失败。

int authRequired(client *c) {
    /* Check if the user is authenticated. This check is skipped in case
     * the default user is flagged as "nopass" and is active. */
    int auth_required = (!(DefaultUser->flags & USER_FLAG_NOPASS) ||
                          (DefaultUser->flags & USER_FLAG_DISABLED)) &&
                        !c->authenticated;
    return auth_required;
}

5.3.2 检测是否有执行权限

int acl_retval = ACLCheckAllPerm(c,&acl_errpos);
    if (acl_retval != ACL_OK) {
        addACLLogEntry(c,acl_retval,acl_errpos,NULL);
        switch (acl_retval) {
        case ACL_DENIED_CMD:
            rejectCommandFormat(c,
                "-NOPERM this user has no permissions to run "
                "the '%s' command or its subcommand", c->cmd->name);
            break;
        case ACL_DENIED_KEY:
            rejectCommandFormat(c,
                "-NOPERM this user has no permissions to access "
                "one of the keys used as arguments");
            break;
        case ACL_DENIED_CHANNEL:
            rejectCommandFormat(c,
                "-NOPERM this user has no permissions to access "
                "one of the channels used as arguments");
            break;
        default:
            rejectCommandFormat(c, "no permission");
            break;
        }
        return C_OK;
    }
5.3.2.1 检测当前用户是否为admin账户

当为user指针为空,则当用户是admin,拥有所有的权限。还没有找到如何成为admin,求指导

	user *u = c->user;

    /* If there is no associated user, the connection can run anything. */
    if (u == NULL) return ACL_OK;
5.3.2.2 检查命令是否有执行权限

如果当前用户有所有命令的权限,或则此命令不需要认证就可以执行(hello,auth),就进入下一步的检查,否则需要检查此命令是否有执行权限。

对于此命令没有执行权限时,再次检查子命令是否有执行权限,如果没有则返回没有权限,否则进行下一步的检查。

    uint64_t id = c->cmd->id;
	...
/* Check if the user can execute this command or if the command
     * doesn't need to be authenticated (hello, auth). */
    if (!(u->flags & USER_FLAG_ALLCOMMANDS) && !(c->cmd->flags & CMD_NO_AUTH))
    {
        /* If the bit is not set we have to check further, in case the
         * command is allowed just with that specific subcommand. */
        if (ACLGetUserCommandBit(u,id) == 0) {
            /* Check if the subcommand matches. */
            if (c->argc < 2 ||
                u->allowed_subcommands == NULL ||
                u->allowed_subcommands[id] == NULL)
            {
                return ACL_DENIED_CMD;
            }

            long subid = 0;
            while (1) {
                if (u->allowed_subcommands[id][subid] == NULL)
                    return ACL_DENIED_CMD;
                if (!strcasecmp(c->argv[1]->ptr,
                                u->allowed_subcommands[id][subid]))
                    break; /* Subcommand match found. Stop here. */
                subid++;
            }
        }
    }
5.3.2.3 检查操作的key是否有权限

对于拥有所有key访问权限的用户,或则此命令没有key处理的函数以及没有参数,则有权限,成功返回了。

否则将检查当前key是否有权限访问。

 /* 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))
    {
        getKeysResult result = GETKEYS_RESULT_INIT;
        int numkeys = getKeysFromCommand(c->cmd,c->argv,c->argc,&result);
        int *keyidx = result.keys;
        for (int j = 0; j < numkeys; j++) {
            listIter li;
            listNode *ln;
            listRewind(u->patterns,&li);

            /* Test this key against every pattern. */
            int match = 0;
            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;
                }
            }
            if (!match) {
                if (keyidxptr) *keyidxptr = keyidx[j];
                getKeysFreeResult(&result);
                return ACL_DENIED_KEY;
            }
        }
        getKeysFreeResult(&result);
    }

    /* If we survived all the above checks, the user can execute the
     * command. */
    return ACL_OK;
5.3.2.3.1 如何检查key的?

根据命令表中对每个命令的表述,可以知道命令需要多少参数,第一个参数在哪个位置,最后一个参数在哪个位置,每个参数间隔多少等等。

struct redisCommand {
    ...
    /* Use a function to determine keys arguments in a command line.
     * Used for Redis Cluster redirect. */
    redisGetKeysProc *getkeys_proc;
    /* What keys should be loaded in background when calling this command? */
    int firstkey; /* The first argument that's a key (0 = no keys) */
    int lastkey;  /* The last argument that's a key */
    int keystep;  /* The step between first and last key */
    ...
}
struct redisCommand redisCommandTable[] = {
    {"module",moduleCommand,-2,
     "admin no-script",
     0,NULL,0,0,0,0,0,0},

    {"get",getCommand,2,
     "read-only fast @string",
     0,NULL,1,1,1,0,0,0},

    {"getex",getexCommand,-2,
     "write fast @string",
     0,NULL,1,1,1,0,0,0},
    ...
};

根据这些获取请求行中key的位置

/* The base case is to use the keys position as given in the command table
 * (firstkey, lastkey, step). */
int getKeysUsingCommandTable(struct redisCommand *cmd,robj **argv, int argc, getKeysResult *result) {
    int j, i = 0, last, *keys;
    UNUSED(argv);

    if (cmd->firstkey == 0) {
        result->numkeys = 0;
        return 0;
    }

    last = cmd->lastkey;
    if (last < 0) last = argc+last;

    int count = ((last - cmd->firstkey)+1);
    keys = getKeysPrepareResult(result, count);

    for (j = cmd->firstkey; j <= last; j += cmd->keystep) {
        if (j >= argc) {
            /* Modules commands, and standard commands with a not fixed number
             * of arguments (negative arity parameter) do not have dispatch
             * time arity checks, so we need to handle the case where the user
             * passed an invalid number of arguments here. In this case we
             * return no keys and expect the command implementation to report
             * an arity or syntax error. */
            if (cmd->flags & CMD_MODULE || cmd->arity < 0) {
                result->numkeys = 0;
                return 0;
            } else {
                serverPanic("Redis built-in command declared keys positions not matching the arity requirements.");
            }
        }
        keys[i++] = j;
    }
    result->numkeys = i;
    return i;
}

遍历所有的参数,逐一的进行检查,检查是在pattern链表中逐一检查是否满足规则。

双重循环,逐一检查,每一个key满足一个规则就可以了,只要有一个key不满足则整个操作不满足条件。

		getKeysResult result = GETKEYS_RESULT_INIT;
        int numkeys = getKeysFromCommand(c->cmd,c->argv,c->argc,&result);
        int *keyidx = result.keys;
        for (int j = 0; j < numkeys; j++) {
            listIter li;
            listNode *ln;
            listRewind(u->patterns,&li);

            /* Test this key against every pattern. */
            int match = 0;
            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;
                }
            }
            if (!match) {
                if (keyidxptr) *keyidxptr = keyidx[j];
                getKeysFreeResult(&result);
                return ACL_DENIED_KEY;
            }
        }
        getKeysFreeResult(&result);
5.3.2.3.2 如何匹配规则的

redis自己实现了规则匹配代码,因为支持的规则就简单,所以没有必要使用那些很笨重的正则表达式库。

/* Glob-style pattern matching. */
int stringmatchlen(const char *pattern, int patternLen,
        const char *string, int stringLen, int nocase)
{
    while(patternLen && stringLen) {
        switch(pattern[0]) {
        case '*':
            while (patternLen && pattern[1] == '*') {
                pattern++;
                patternLen--;
            }
            if (patternLen == 1)
                return 1; /* match */
            while(stringLen) {
                if (stringmatchlen(pattern+1, patternLen-1,
                            string, stringLen, nocase))
                    return 1; /* match */
                string++;
                stringLen--;
            }
            return 0; /* no match */
            break;
        case '?':
            string++;
            stringLen--;
            break;
        case '[':
        {
            int not, match;

            pattern++;
            patternLen--;
            not = pattern[0] == '^';
            if (not) {
                pattern++;
                patternLen--;
            }
            match = 0;
            while(1) {
                if (pattern[0] == '\\' && patternLen >= 2) {
                    pattern++;
                    patternLen--;
                    if (pattern[0] == string[0])
                        match = 1;
                } else if (pattern[0] == ']') {
                    break;
                } else if (patternLen == 0) {
                    pattern--;
                    patternLen++;
                    break;
                } else if (patternLen >= 3 && pattern[1] == '-') {
                    int start = pattern[0];
                    int end = pattern[2];
                    int c = string[0];
                    if (start > end) {
                        int t = start;
                        start = end;
                        end = t;
                    }
                    if (nocase) {
                        start = tolower(start);
                        end = tolower(end);
                        c = tolower(c);
                    }
                    pattern += 2;
                    patternLen -= 2;
                    if (c >= start && c <= end)
                        match = 1;
                } else {
                    if (!nocase) {
                        if (pattern[0] == string[0])
                            match = 1;
                    } else {
                        if (tolower((int)pattern[0]) == tolower((int)string[0]))
                            match = 1;
                    }
                }
                pattern++;
                patternLen--;
            }
            if (not)
                match = !match;
            if (!match)
                return 0; /* no match */
            string++;
            stringLen--;
            break;
        }
        case '\\':
            if (patternLen >= 2) {
                pattern++;
                patternLen--;
            }
            /* fall through */
        default:
            if (!nocase) {
                if (pattern[0] != string[0])
                    return 0; /* no match */
            } else {
                if (tolower((int)pattern[0]) != tolower((int)string[0]))
                    return 0; /* no match */
            }
            string++;
            stringLen--;
            break;
        }
        pattern++;
        patternLen--;
        if (stringLen == 0) {
            while(*pattern == '*') {
                pattern++;
                patternLen--;
            }
            break;
        }
    }
    if (patternLen == 0 && stringLen == 0)
        return 1;
    return 0;
}
5.3.2.4 检查channel是否有权限

key的权限检查通过后,判断是否是pub/sub操作,如果是,则需要检查channel的权限。

 if (c->cmd->proc == publishCommand)
        acl_retval = ACLCheckPubsubPerm(c,1,1,0,idxptr);
    else if (c->cmd->proc == subscribeCommand)
        acl_retval = ACLCheckPubsubPerm(c,1,c->argc-1,0,idxptr);
    else if (c->cmd->proc == psubscribeCommand)
        acl_retval = ACLCheckPubsubPerm(c,1,c->argc-1,1,idxptr);

如果是admin或者此用户拥有所有channel权限,则直接通过,否则进行检查是否有权限。

int ACLCheckPubsubPerm(client *c, int idx, int count, int literal, int *idxptr) {
    user *u = c->user;

    /* If there is no associated user, the connection can run anything. */
    if (u == NULL) return ACL_OK;

    /* Check if the user can access the channels mentioned in the command's
     * arguments. */
    if (!(c->user->flags & USER_FLAG_ALLCHANNELS)) {
        for (int j = idx; j < idx+count; j++) {
            if (ACLCheckPubsubChannelPerm(c->argv[j]->ptr,u->channels,literal)
                != ACL_OK) {
                if (idxptr) *idxptr = j;
                return ACL_DENIED_CHANNEL;
            }
        }
    }

    /* If we survived all the above checks, the user can execute the
     * command. */
    return ACL_OK;
}

对于publishCommand和subscribeCommand使用正则匹配,psubscribeCommand使用全等,因为psubscribeCommand的key本身就是规则形式的。

int ACLCheckPubsubChannelPerm(sds channel, list *allowed, int literal) {
    listIter li;
    listNode *ln;
    size_t clen = sdslen(channel);
    int match = 0;

    listRewind(allowed,&li);
    while((ln = listNext(&li))) {
        sds pattern = listNodeValue(ln);
        size_t plen = sdslen(pattern);

        if ((literal && !sdscmp(pattern,channel)) || 
            (!literal && stringmatchlen(pattern,plen,channel,clen,0)))
        {
            match = 1;
            break;
        }
    }
    if (!match) {
        return ACL_DENIED_CHANNEL;
    }
    return ACL_OK;
}
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值