File: | src/acl.c |
Warning: | line 527, column 18 Assigned value is garbage or undefined |
Press '?' to see keyboard shortcuts
Keyboard shortcuts:
1 | /* | |||
2 | * Copyright (c) 2018, Salvatore Sanfilippo <antirez at gmail dot com> | |||
3 | * All rights reserved. | |||
4 | * | |||
5 | * Redistribution and use in source and binary forms, with or without | |||
6 | * modification, are permitted provided that the following conditions are met: | |||
7 | * | |||
8 | * * Redistributions of source code must retain the above copyright notice, | |||
9 | * this list of conditions and the following disclaimer. | |||
10 | * * Redistributions in binary form must reproduce the above copyright | |||
11 | * notice, this list of conditions and the following disclaimer in the | |||
12 | * documentation and/or other materials provided with the distribution. | |||
13 | * * Neither the name of Redis nor the names of its contributors may be used | |||
14 | * to endorse or promote products derived from this software without | |||
15 | * specific prior written permission. | |||
16 | * | |||
17 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" | |||
18 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | |||
19 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE | |||
20 | * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE | |||
21 | * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR | |||
22 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF | |||
23 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS | |||
24 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN | |||
25 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) | |||
26 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE | |||
27 | * POSSIBILITY OF SUCH DAMAGE. | |||
28 | */ | |||
29 | ||||
30 | #include "server.h" | |||
31 | #include "sha256.h" | |||
32 | #include <fcntl.h> | |||
33 | #include <ctype.h> | |||
34 | ||||
35 | /* ============================================================================= | |||
36 | * Global state for ACLs | |||
37 | * ==========================================================================*/ | |||
38 | ||||
39 | rax *Users; /* Table mapping usernames to user structures. */ | |||
40 | ||||
41 | user *DefaultUser; /* Global reference to the default user. | |||
42 | Every new connection is associated to it, if no | |||
43 | AUTH or HELLO is used to authenticate with a | |||
44 | different user. */ | |||
45 | ||||
46 | list *UsersToLoad; /* This is a list of users found in the configuration file | |||
47 | that we'll need to load in the final stage of Redis | |||
48 | initialization, after all the modules are already | |||
49 | loaded. Every list element is a NULL terminated | |||
50 | array of SDS pointers: the first is the user name, | |||
51 | all the remaining pointers are ACL rules in the same | |||
52 | format as ACLSetUser(). */ | |||
53 | list *ACLLog; /* Our security log, the user is able to inspect that | |||
54 | using the ACL LOG command .*/ | |||
55 | ||||
56 | static rax *commandId = NULL((void*)0); /* Command name to id mapping */ | |||
57 | ||||
58 | static unsigned long nextid = 0; /* Next command id that has not been assigned */ | |||
59 | ||||
60 | struct ACLCategoryItem { | |||
61 | const char *name; | |||
62 | uint64_t flag; | |||
63 | } ACLCommandCategories[] = { | |||
64 | {"keyspace", CMD_CATEGORY_KEYSPACE(1ULL<<19)}, | |||
65 | {"read", CMD_CATEGORY_READ(1ULL<<20)}, | |||
66 | {"write", CMD_CATEGORY_WRITE(1ULL<<21)}, | |||
67 | {"set", CMD_CATEGORY_SET(1ULL<<22)}, | |||
68 | {"sortedset", CMD_CATEGORY_SORTEDSET(1ULL<<23)}, | |||
69 | {"list", CMD_CATEGORY_LIST(1ULL<<24)}, | |||
70 | {"hash", CMD_CATEGORY_HASH(1ULL<<25)}, | |||
71 | {"string", CMD_CATEGORY_STRING(1ULL<<26)}, | |||
72 | {"bitmap", CMD_CATEGORY_BITMAP(1ULL<<27)}, | |||
73 | {"hyperloglog", CMD_CATEGORY_HYPERLOGLOG(1ULL<<28)}, | |||
74 | {"geo", CMD_CATEGORY_GEO(1ULL<<29)}, | |||
75 | {"stream", CMD_CATEGORY_STREAM(1ULL<<30)}, | |||
76 | {"pubsub", CMD_CATEGORY_PUBSUB(1ULL<<31)}, | |||
77 | {"admin", CMD_CATEGORY_ADMIN(1ULL<<32)}, | |||
78 | {"fast", CMD_CATEGORY_FAST(1ULL<<33)}, | |||
79 | {"slow", CMD_CATEGORY_SLOW(1ULL<<34)}, | |||
80 | {"blocking", CMD_CATEGORY_BLOCKING(1ULL<<35)}, | |||
81 | {"dangerous", CMD_CATEGORY_DANGEROUS(1ULL<<36)}, | |||
82 | {"connection", CMD_CATEGORY_CONNECTION(1ULL<<37)}, | |||
83 | {"transaction", CMD_CATEGORY_TRANSACTION(1ULL<<38)}, | |||
84 | {"scripting", CMD_CATEGORY_SCRIPTING(1ULL<<39)}, | |||
85 | {NULL((void*)0),0} /* Terminator. */ | |||
86 | }; | |||
87 | ||||
88 | struct ACLUserFlag { | |||
89 | const char *name; | |||
90 | uint64_t flag; | |||
91 | } ACLUserFlags[] = { | |||
92 | /* Note: the order here dictates the emitted order at ACLDescribeUser */ | |||
93 | {"on", USER_FLAG_ENABLED(1<<0)}, | |||
94 | {"off", USER_FLAG_DISABLED(1<<1)}, | |||
95 | {"allkeys", USER_FLAG_ALLKEYS(1<<2)}, | |||
96 | {"allchannels", USER_FLAG_ALLCHANNELS(1<<5)}, | |||
97 | {"allcommands", USER_FLAG_ALLCOMMANDS(1<<3)}, | |||
98 | {"nopass", USER_FLAG_NOPASS(1<<4)}, | |||
99 | {"skip-sanitize-payload", USER_FLAG_SANITIZE_PAYLOAD_SKIP(1<<7)}, | |||
100 | {"sanitize-payload", USER_FLAG_SANITIZE_PAYLOAD(1<<6)}, | |||
101 | {NULL((void*)0),0} /* Terminator. */ | |||
102 | }; | |||
103 | ||||
104 | void ACLResetSubcommandsForCommand(user *u, unsigned long id); | |||
105 | void ACLResetSubcommands(user *u); | |||
106 | void ACLAddAllowedSubcommand(user *u, unsigned long id, const char *sub); | |||
107 | void ACLFreeLogEntry(void *le); | |||
108 | ||||
109 | /* The length of the string representation of a hashed password. */ | |||
110 | #define HASH_PASSWORD_LEN32*2 SHA256_BLOCK_SIZE32*2 | |||
111 | ||||
112 | /* ============================================================================= | |||
113 | * Helper functions for the rest of the ACL implementation | |||
114 | * ==========================================================================*/ | |||
115 | ||||
116 | /* Return zero if strings are the same, non-zero if they are not. | |||
117 | * The comparison is performed in a way that prevents an attacker to obtain | |||
118 | * information about the nature of the strings just monitoring the execution | |||
119 | * time of the function. | |||
120 | * | |||
121 | * Note that limiting the comparison length to strings up to 512 bytes we | |||
122 | * can avoid leaking any information about the password length and any | |||
123 | * possible branch misprediction related leak. | |||
124 | */ | |||
125 | int time_independent_strcmp(char *a, char *b) { | |||
126 | char bufa[CONFIG_AUTHPASS_MAX_LEN512], bufb[CONFIG_AUTHPASS_MAX_LEN512]; | |||
127 | /* The above two strlen perform len(a) + len(b) operations where either | |||
128 | * a or b are fixed (our password) length, and the difference is only | |||
129 | * relative to the length of the user provided string, so no information | |||
130 | * leak is possible in the following two lines of code. */ | |||
131 | unsigned int alen = strlen(a); | |||
132 | unsigned int blen = strlen(b); | |||
133 | unsigned int j; | |||
134 | int diff = 0; | |||
135 | ||||
136 | /* We can't compare strings longer than our static buffers. | |||
137 | * Note that this will never pass the first test in practical circumstances | |||
138 | * so there is no info leak. */ | |||
139 | if (alen > sizeof(bufa) || blen > sizeof(bufb)) return 1; | |||
140 | ||||
141 | memset(bufa,0,sizeof(bufa)); /* Constant time. */ | |||
142 | memset(bufb,0,sizeof(bufb)); /* Constant time. */ | |||
143 | /* Again the time of the following two copies is proportional to | |||
144 | * len(a) + len(b) so no info is leaked. */ | |||
145 | memcpy(bufa,a,alen); | |||
146 | memcpy(bufb,b,blen); | |||
147 | ||||
148 | /* Always compare all the chars in the two buffers without | |||
149 | * conditional expressions. */ | |||
150 | for (j = 0; j < sizeof(bufa); j++) { | |||
151 | diff |= (bufa[j] ^ bufb[j]); | |||
152 | } | |||
153 | /* Length must be equal as well. */ | |||
154 | diff |= alen ^ blen; | |||
155 | return diff; /* If zero strings are the same. */ | |||
156 | } | |||
157 | ||||
158 | /* Given an SDS string, returns the SHA256 hex representation as a | |||
159 | * new SDS string. */ | |||
160 | sds ACLHashPassword(unsigned char *cleartext, size_t len) { | |||
161 | SHA256_CTX ctx; | |||
162 | unsigned char hash[SHA256_BLOCK_SIZE32]; | |||
163 | char hex[HASH_PASSWORD_LEN32*2]; | |||
164 | char *cset = "0123456789abcdef"; | |||
165 | ||||
166 | sha256_init(&ctx); | |||
167 | sha256_update(&ctx,(unsigned char*)cleartext,len); | |||
168 | sha256_final(&ctx,hash); | |||
169 | ||||
170 | for (int j = 0; j < SHA256_BLOCK_SIZE32; j++) { | |||
171 | hex[j*2] = cset[((hash[j]&0xF0)>>4)]; | |||
172 | hex[j*2+1] = cset[(hash[j]&0xF)]; | |||
173 | } | |||
174 | return sdsnewlen(hex,HASH_PASSWORD_LEN32*2); | |||
175 | } | |||
176 | ||||
177 | /* Given a hash and the hash length, returns C_OK if it is a valid password | |||
178 | * hash, or C_ERR otherwise. */ | |||
179 | int ACLCheckPasswordHash(unsigned char *hash, int hashlen) { | |||
180 | if (hashlen != HASH_PASSWORD_LEN32*2) { | |||
181 | return C_ERR-1; | |||
182 | } | |||
183 | ||||
184 | /* Password hashes can only be characters that represent | |||
185 | * hexadecimal values, which are numbers and lowercase | |||
186 | * characters 'a' through 'f'. */ | |||
187 | for(int i = 0; i < HASH_PASSWORD_LEN32*2; i++) { | |||
188 | char c = hash[i]; | |||
189 | if ((c < 'a' || c > 'f') && (c < '0' || c > '9')) { | |||
190 | return C_ERR-1; | |||
191 | } | |||
192 | } | |||
193 | return C_OK0; | |||
194 | } | |||
195 | ||||
196 | /* ============================================================================= | |||
197 | * Low level ACL API | |||
198 | * ==========================================================================*/ | |||
199 | ||||
200 | /* Return 1 if the specified string contains spaces or null characters. | |||
201 | * We do this for usernames and key patterns for simpler rewriting of | |||
202 | * ACL rules, presentation on ACL list, and to avoid subtle security bugs | |||
203 | * that may arise from parsing the rules in presence of escapes. | |||
204 | * The function returns 0 if the string has no spaces. */ | |||
205 | int ACLStringHasSpaces(const char *s, size_t len) { | |||
206 | for (size_t i = 0; i < len; i++) { | |||
207 | if (isspace(s[i])((*__ctype_b_loc ())[(int) ((s[i]))] & (unsigned short int ) _ISspace) || s[i] == 0) return 1; | |||
208 | } | |||
209 | return 0; | |||
210 | } | |||
211 | ||||
212 | /* Given the category name the command returns the corresponding flag, or | |||
213 | * zero if there is no match. */ | |||
214 | uint64_t ACLGetCommandCategoryFlagByName(const char *name) { | |||
215 | for (int j = 0; ACLCommandCategories[j].flag != 0; j++) { | |||
216 | if (!strcasecmp(name,ACLCommandCategories[j].name)) { | |||
217 | return ACLCommandCategories[j].flag; | |||
218 | } | |||
219 | } | |||
220 | return 0; /* No match. */ | |||
221 | } | |||
222 | ||||
223 | /* Method for passwords/pattern comparison used for the user->passwords list | |||
224 | * so that we can search for items with listSearchKey(). */ | |||
225 | int ACLListMatchSds(void *a, void *b) { | |||
226 | return sdscmp(a,b) == 0; | |||
227 | } | |||
228 | ||||
229 | /* Method to free list elements from ACL users password/patterns lists. */ | |||
230 | void ACLListFreeSds(void *item) { | |||
231 | sdsfree(item); | |||
232 | } | |||
233 | ||||
234 | /* Method to duplicate list elements from ACL users password/patterns lists. */ | |||
235 | void *ACLListDupSds(void *item) { | |||
236 | return sdsdup(item); | |||
237 | } | |||
238 | ||||
239 | /* Create a new user with the specified name, store it in the list | |||
240 | * of users (the Users global radix tree), and returns a reference to | |||
241 | * the structure representing the user. | |||
242 | * | |||
243 | * If the user with such name already exists NULL is returned. */ | |||
244 | user *ACLCreateUser(const char *name, size_t namelen) { | |||
245 | if (raxFind(Users,(unsigned char*)name,namelen) != raxNotFound) return NULL((void*)0); | |||
246 | user *u = zmalloc(sizeof(*u)); | |||
247 | u->name = sdsnewlen(name,namelen); | |||
248 | u->flags = USER_FLAG_DISABLED(1<<1) | server.acl_pubusub_default; | |||
249 | u->allowed_subcommands = NULL((void*)0); | |||
250 | u->passwords = listCreate(); | |||
251 | u->patterns = listCreate(); | |||
252 | u->channels = listCreate(); | |||
253 | listSetMatchMethod(u->passwords,ACLListMatchSds)((u->passwords)->match = (ACLListMatchSds)); | |||
254 | listSetFreeMethod(u->passwords,ACLListFreeSds)((u->passwords)->free = (ACLListFreeSds)); | |||
255 | listSetDupMethod(u->passwords,ACLListDupSds)((u->passwords)->dup = (ACLListDupSds)); | |||
256 | listSetMatchMethod(u->patterns,ACLListMatchSds)((u->patterns)->match = (ACLListMatchSds)); | |||
257 | listSetFreeMethod(u->patterns,ACLListFreeSds)((u->patterns)->free = (ACLListFreeSds)); | |||
258 | listSetDupMethod(u->patterns,ACLListDupSds)((u->patterns)->dup = (ACLListDupSds)); | |||
259 | listSetMatchMethod(u->channels,ACLListMatchSds)((u->channels)->match = (ACLListMatchSds)); | |||
260 | listSetFreeMethod(u->channels,ACLListFreeSds)((u->channels)->free = (ACLListFreeSds)); | |||
261 | listSetDupMethod(u->channels,ACLListDupSds)((u->channels)->dup = (ACLListDupSds)); | |||
262 | memset(u->allowed_commands,0,sizeof(u->allowed_commands)); | |||
263 | raxInsert(Users,(unsigned char*)name,namelen,u,NULL((void*)0)); | |||
264 | return u; | |||
265 | } | |||
266 | ||||
267 | /* This function should be called when we need an unlinked "fake" user | |||
268 | * we can use in order to validate ACL rules or for other similar reasons. | |||
269 | * The user will not get linked to the Users radix tree. The returned | |||
270 | * user should be released with ACLFreeUser() as usually. */ | |||
271 | user *ACLCreateUnlinkedUser(void) { | |||
272 | char username[64]; | |||
273 | for (int j = 0; ; j++) { | |||
274 | snprintf(username,sizeof(username),"__fakeuser:%d__",j); | |||
275 | user *fakeuser = ACLCreateUser(username,strlen(username)); | |||
276 | if (fakeuser == NULL((void*)0)) continue; | |||
277 | int retval = raxRemove(Users,(unsigned char*) username, | |||
278 | strlen(username),NULL((void*)0)); | |||
279 | serverAssert(retval != 0)((retval != 0)?(void)0 : (_serverAssert("retval != 0","acl.c" ,279),__builtin_unreachable())); | |||
280 | return fakeuser; | |||
281 | } | |||
282 | } | |||
283 | ||||
284 | /* Release the memory used by the user structure. Note that this function | |||
285 | * will not remove the user from the Users global radix tree. */ | |||
286 | void ACLFreeUser(user *u) { | |||
287 | sdsfree(u->name); | |||
288 | listRelease(u->passwords); | |||
289 | listRelease(u->patterns); | |||
290 | listRelease(u->channels); | |||
291 | ACLResetSubcommands(u); | |||
292 | zfree(u); | |||
293 | } | |||
294 | ||||
295 | /* When a user is deleted we need to cycle the active | |||
296 | * connections in order to kill all the pending ones that | |||
297 | * are authenticated with such user. */ | |||
298 | void ACLFreeUserAndKillClients(user *u) { | |||
299 | listIter li; | |||
300 | listNode *ln; | |||
301 | listRewind(server.clients,&li); | |||
302 | while ((ln = listNext(&li)) != NULL((void*)0)) { | |||
303 | client *c = listNodeValue(ln)((ln)->value); | |||
304 | if (c->user == u) { | |||
305 | /* We'll free the connection asynchronously, so | |||
306 | * in theory to set a different user is not needed. | |||
307 | * However if there are bugs in Redis, soon or later | |||
308 | * this may result in some security hole: it's much | |||
309 | * more defensive to set the default user and put | |||
310 | * it in non authenticated mode. */ | |||
311 | c->user = DefaultUser; | |||
312 | c->authenticated = 0; | |||
313 | /* We will write replies to this client later, so we can't | |||
314 | * close it directly even if async. */ | |||
315 | if (c == server.current_client) { | |||
316 | c->flags |= CLIENT_CLOSE_AFTER_COMMAND(1ULL<<40); | |||
317 | } else { | |||
318 | freeClientAsync(c); | |||
319 | } | |||
320 | } | |||
321 | } | |||
322 | ACLFreeUser(u); | |||
323 | } | |||
324 | ||||
325 | /* Copy the user ACL rules from the source user 'src' to the destination | |||
326 | * user 'dst' so that at the end of the process they'll have exactly the | |||
327 | * same rules (but the names will continue to be the original ones). */ | |||
328 | void ACLCopyUser(user *dst, user *src) { | |||
329 | listRelease(dst->passwords); | |||
330 | listRelease(dst->patterns); | |||
331 | listRelease(dst->channels); | |||
332 | dst->passwords = listDup(src->passwords); | |||
333 | dst->patterns = listDup(src->patterns); | |||
334 | dst->channels = listDup(src->channels); | |||
335 | memcpy(dst->allowed_commands,src->allowed_commands, | |||
336 | sizeof(dst->allowed_commands)); | |||
337 | dst->flags = src->flags; | |||
338 | ACLResetSubcommands(dst); | |||
339 | /* Copy the allowed subcommands array of array of SDS strings. */ | |||
340 | if (src->allowed_subcommands) { | |||
341 | for (int j = 0; j < USER_COMMAND_BITS_COUNT1024; j++) { | |||
342 | if (src->allowed_subcommands[j]) { | |||
343 | for (int i = 0; src->allowed_subcommands[j][i]; i++) | |||
344 | { | |||
345 | ACLAddAllowedSubcommand(dst, j, | |||
346 | src->allowed_subcommands[j][i]); | |||
347 | } | |||
348 | } | |||
349 | } | |||
350 | } | |||
351 | } | |||
352 | ||||
353 | /* Free all the users registered in the radix tree 'users' and free the | |||
354 | * radix tree itself. */ | |||
355 | void ACLFreeUsersSet(rax *users) { | |||
356 | raxFreeWithCallback(users,(void(*)(void*))ACLFreeUserAndKillClients); | |||
357 | } | |||
358 | ||||
359 | /* Given a command ID, this function set by reference 'word' and 'bit' | |||
360 | * so that user->allowed_commands[word] will address the right word | |||
361 | * where the corresponding bit for the provided ID is stored, and | |||
362 | * so that user->allowed_commands[word]&bit will identify that specific | |||
363 | * bit. The function returns C_ERR in case the specified ID overflows | |||
364 | * the bitmap in the user representation. */ | |||
365 | int ACLGetCommandBitCoordinates(uint64_t id, uint64_t *word, uint64_t *bit) { | |||
366 | if (id >= USER_COMMAND_BITS_COUNT1024) return C_ERR-1; | |||
367 | *word = id / sizeof(uint64_t) / 8; | |||
368 | *bit = 1ULL << (id % (sizeof(uint64_t) * 8)); | |||
369 | return C_OK0; | |||
370 | } | |||
371 | ||||
372 | /* Check if the specified command bit is set for the specified user. | |||
373 | * The function returns 1 is the bit is set or 0 if it is not. | |||
374 | * Note that this function does not check the ALLCOMMANDS flag of the user | |||
375 | * but just the lowlevel bitmask. | |||
376 | * | |||
377 | * If the bit overflows the user internal representation, zero is returned | |||
378 | * in order to disallow the execution of the command in such edge case. */ | |||
379 | int ACLGetUserCommandBit(user *u, unsigned long id) { | |||
380 | uint64_t word, bit; | |||
381 | if (ACLGetCommandBitCoordinates(id,&word,&bit) == C_ERR-1) return 0; | |||
382 | return (u->allowed_commands[word] & bit) != 0; | |||
383 | } | |||
384 | ||||
385 | /* When +@all or allcommands is given, we set a reserved bit as well that we | |||
386 | * can later test, to see if the user has the right to execute "future commands", | |||
387 | * that is, commands loaded later via modules. */ | |||
388 | int ACLUserCanExecuteFutureCommands(user *u) { | |||
389 | return ACLGetUserCommandBit(u,USER_COMMAND_BITS_COUNT1024-1); | |||
390 | } | |||
391 | ||||
392 | /* Set the specified command bit for the specified user to 'value' (0 or 1). | |||
393 | * If the bit overflows the user internal representation, no operation | |||
394 | * is performed. As a side effect of calling this function with a value of | |||
395 | * zero, the user flag ALLCOMMANDS is cleared since it is no longer possible | |||
396 | * to skip the command bit explicit test. */ | |||
397 | void ACLSetUserCommandBit(user *u, unsigned long id, int value) { | |||
398 | uint64_t word, bit; | |||
399 | if (ACLGetCommandBitCoordinates(id,&word,&bit) == C_ERR-1) return; | |||
400 | if (value) { | |||
401 | u->allowed_commands[word] |= bit; | |||
402 | } else { | |||
403 | u->allowed_commands[word] &= ~bit; | |||
404 | u->flags &= ~USER_FLAG_ALLCOMMANDS(1<<3); | |||
405 | } | |||
406 | } | |||
407 | ||||
408 | /* This is like ACLSetUserCommandBit(), but instead of setting the specified | |||
409 | * ID, it will check all the commands in the category specified as argument, | |||
410 | * and will set all the bits corresponding to such commands to the specified | |||
411 | * value. Since the category passed by the user may be non existing, the | |||
412 | * function returns C_ERR if the category was not found, or C_OK if it was | |||
413 | * found and the operation was performed. */ | |||
414 | int ACLSetUserCommandBitsForCategory(user *u, const char *category, int value) { | |||
415 | uint64_t cflag = ACLGetCommandCategoryFlagByName(category); | |||
416 | if (!cflag) return C_ERR-1; | |||
417 | dictIterator *di = dictGetIterator(server.orig_commands); | |||
418 | dictEntry *de; | |||
419 | while ((de = dictNext(di)) != NULL((void*)0)) { | |||
420 | struct redisCommand *cmd = dictGetVal(de)((de)->v.val); | |||
421 | if (cmd->flags & CMD_MODULE(1ULL<<3)) continue; /* Ignore modules commands. */ | |||
422 | if (cmd->flags & cflag) { | |||
423 | ACLSetUserCommandBit(u,cmd->id,value); | |||
424 | ACLResetSubcommandsForCommand(u,cmd->id); | |||
425 | } | |||
426 | } | |||
427 | dictReleaseIterator(di); | |||
428 | return C_OK0; | |||
429 | } | |||
430 | ||||
431 | /* Return the number of commands allowed (on) and denied (off) for the user 'u' | |||
432 | * in the subset of commands flagged with the specified category name. | |||
433 | * If the category name is not valid, C_ERR is returned, otherwise C_OK is | |||
434 | * returned and on and off are populated by reference. */ | |||
435 | int ACLCountCategoryBitsForUser(user *u, unsigned long *on, unsigned long *off, | |||
436 | const char *category) | |||
437 | { | |||
438 | uint64_t cflag = ACLGetCommandCategoryFlagByName(category); | |||
439 | if (!cflag) return C_ERR-1; | |||
440 | ||||
441 | *on = *off = 0; | |||
442 | dictIterator *di = dictGetIterator(server.orig_commands); | |||
443 | dictEntry *de; | |||
444 | while ((de = dictNext(di)) != NULL((void*)0)) { | |||
445 | struct redisCommand *cmd = dictGetVal(de)((de)->v.val); | |||
446 | if (cmd->flags & cflag) { | |||
447 | if (ACLGetUserCommandBit(u,cmd->id)) | |||
448 | (*on)++; | |||
449 | else | |||
450 | (*off)++; | |||
451 | } | |||
452 | } | |||
453 | dictReleaseIterator(di); | |||
454 | return C_OK0; | |||
455 | } | |||
456 | ||||
457 | /* This function returns an SDS string representing the specified user ACL | |||
458 | * rules related to command execution, in the same format you could set them | |||
459 | * back using ACL SETUSER. The function will return just the set of rules needed | |||
460 | * to recreate the user commands bitmap, without including other user flags such | |||
461 | * as on/off, passwords and so forth. The returned string always starts with | |||
462 | * the +@all or -@all rule, depending on the user bitmap, and is followed, if | |||
463 | * needed, by the other rules needed to narrow or extend what the user can do. */ | |||
464 | sds ACLDescribeUserCommandRules(user *u) { | |||
465 | sds rules = sdsempty(); | |||
466 | int additive; /* If true we start from -@all and add, otherwise if | |||
467 | false we start from +@all and remove. */ | |||
468 | ||||
469 | /* This code is based on a trick: as we generate the rules, we apply | |||
470 | * them to a fake user, so that as we go we still know what are the | |||
471 | * bit differences we should try to address by emitting more rules. */ | |||
472 | user fu = {0}; | |||
473 | user *fakeuser = &fu; | |||
474 | ||||
475 | /* Here we want to understand if we should start with +@all and remove | |||
476 | * the commands corresponding to the bits that are not set in the user | |||
477 | * commands bitmap, or the contrary. Note that semantically the two are | |||
478 | * different. For instance starting with +@all and subtracting, the user | |||
479 | * will be able to execute future commands, while -@all and adding will just | |||
480 | * allow the user the run the selected commands and/or categories. | |||
481 | * How do we test for that? We use the trick of a reserved command ID bit | |||
482 | * that is set only by +@all (and its alias "allcommands"). */ | |||
483 | if (ACLUserCanExecuteFutureCommands(u)) { | |||
484 | additive = 0; | |||
485 | rules = sdscat(rules,"+@all "); | |||
486 | ACLSetUser(fakeuser,"+@all",-1); | |||
487 | } else { | |||
488 | additive = 1; | |||
489 | rules = sdscat(rules,"-@all "); | |||
490 | ACLSetUser(fakeuser,"-@all",-1); | |||
491 | } | |||
492 | ||||
493 | /* Attempt to find a good approximation for categories and commands | |||
494 | * based on the current bits used, by looping over the category list | |||
495 | * and applying the best fit each time. Often a set of categories will not | |||
496 | * perfectly match the set of commands into it, so at the end we do a | |||
497 | * final pass adding/removing the single commands needed to make the bitmap | |||
498 | * exactly match. A temp user is maintained to keep track of categories | |||
499 | * already applied. */ | |||
500 | user tu = {0}; | |||
501 | user *tempuser = &tu; | |||
502 | ||||
503 | /* Keep track of the categories that have been applied, to prevent | |||
504 | * applying them twice. */ | |||
505 | char applied[sizeof(ACLCommandCategories)/sizeof(ACLCommandCategories[0])]; | |||
506 | memset(applied, 0, sizeof(applied)); | |||
507 | ||||
508 | memcpy(tempuser->allowed_commands, | |||
509 | u->allowed_commands, | |||
510 | sizeof(u->allowed_commands)); | |||
511 | while (1) { | |||
512 | int best = -1; | |||
513 | unsigned long mindiff = INT_MAX2147483647, maxsame = 0; | |||
514 | for (int j = 0; ACLCommandCategories[j].flag != 0; j++) { | |||
515 | if (applied[j]) continue; | |||
516 | ||||
517 | unsigned long on, off, diff, same; | |||
518 | ACLCountCategoryBitsForUser(tempuser,&on,&off,ACLCommandCategories[j].name); | |||
519 | /* Check if the current category is the best this loop: | |||
520 | * * It has more commands in common with the user than commands | |||
521 | * that are different. | |||
522 | * AND EITHER | |||
523 | * * It has the fewest number of differences | |||
524 | * than the best match we have found so far. | |||
525 | * * OR it matches the fewest number of differences | |||
526 | * that we've seen but it has more in common. */ | |||
527 | diff = additive
| |||
| ||||
528 | same = additive ? on : off; | |||
529 | if (same > diff && | |||
530 | ((diff < mindiff) || (diff == mindiff && same > maxsame))) | |||
531 | { | |||
532 | best = j; | |||
533 | mindiff = diff; | |||
534 | maxsame = same; | |||
535 | } | |||
536 | } | |||
537 | ||||
538 | /* We didn't find a match */ | |||
539 | if (best == -1) break; | |||
540 | ||||
541 | sds op = sdsnewlen(additive ? "+@" : "-@", 2); | |||
542 | op = sdscat(op,ACLCommandCategories[best].name); | |||
543 | ACLSetUser(fakeuser,op,-1); | |||
544 | ||||
545 | sds invop = sdsnewlen(additive ? "-@" : "+@", 2); | |||
546 | invop = sdscat(invop,ACLCommandCategories[best].name); | |||
547 | ACLSetUser(tempuser,invop,-1); | |||
548 | ||||
549 | rules = sdscatsds(rules,op); | |||
550 | rules = sdscatlen(rules," ",1); | |||
551 | sdsfree(op); | |||
552 | sdsfree(invop); | |||
553 | ||||
554 | applied[best] = 1; | |||
555 | } | |||
556 | ||||
557 | /* Fix the final ACLs with single commands differences. */ | |||
558 | dictIterator *di = dictGetIterator(server.orig_commands); | |||
559 | dictEntry *de; | |||
560 | while ((de = dictNext(di)) != NULL((void*)0)) { | |||
561 | struct redisCommand *cmd = dictGetVal(de)((de)->v.val); | |||
562 | int userbit = ACLGetUserCommandBit(u,cmd->id); | |||
563 | int fakebit = ACLGetUserCommandBit(fakeuser,cmd->id); | |||
564 | if (userbit != fakebit) { | |||
565 | rules = sdscatlen(rules, userbit ? "+" : "-", 1); | |||
566 | rules = sdscat(rules,cmd->name); | |||
567 | rules = sdscatlen(rules," ",1); | |||
568 | ACLSetUserCommandBit(fakeuser,cmd->id,userbit); | |||
569 | } | |||
570 | ||||
571 | /* Emit the subcommands if there are any. */ | |||
572 | if (userbit == 0 && u->allowed_subcommands && | |||
573 | u->allowed_subcommands[cmd->id]) | |||
574 | { | |||
575 | for (int j = 0; u->allowed_subcommands[cmd->id][j]; j++) { | |||
576 | rules = sdscatlen(rules,"+",1); | |||
577 | rules = sdscat(rules,cmd->name); | |||
578 | rules = sdscatlen(rules,"|",1); | |||
579 | rules = sdscatsds(rules,u->allowed_subcommands[cmd->id][j]); | |||
580 | rules = sdscatlen(rules," ",1); | |||
581 | } | |||
582 | } | |||
583 | } | |||
584 | dictReleaseIterator(di); | |||
585 | ||||
586 | /* Trim the final useless space. */ | |||
587 | sdsrange(rules,0,-2); | |||
588 | ||||
589 | /* This is technically not needed, but we want to verify that now the | |||
590 | * predicted bitmap is exactly the same as the user bitmap, and abort | |||
591 | * otherwise, because aborting is better than a security risk in this | |||
592 | * code path. */ | |||
593 | if (memcmp(fakeuser->allowed_commands, | |||
594 | u->allowed_commands, | |||
595 | sizeof(u->allowed_commands)) != 0) | |||
596 | { | |||
597 | serverLog(LL_WARNING3, | |||
598 | "CRITICAL ERROR: User ACLs don't match final bitmap: '%s'", | |||
599 | rules); | |||
600 | serverPanic("No bitmap match in ACLDescribeUserCommandRules()")_serverPanic("acl.c",600,"No bitmap match in ACLDescribeUserCommandRules()" ),__builtin_unreachable(); | |||
601 | } | |||
602 | return rules; | |||
603 | } | |||
604 | ||||
605 | /* This is similar to ACLDescribeUserCommandRules(), however instead of | |||
606 | * describing just the user command rules, everything is described: user | |||
607 | * flags, keys, passwords and finally the command rules obtained via | |||
608 | * the ACLDescribeUserCommandRules() function. This is the function we call | |||
609 | * when we want to rewrite the configuration files describing ACLs and | |||
610 | * in order to show users with ACL LIST. */ | |||
611 | sds ACLDescribeUser(user *u) { | |||
612 | sds res = sdsempty(); | |||
613 | ||||
614 | /* Flags. */ | |||
615 | for (int j = 0; ACLUserFlags[j].flag; j++) { | |||
616 | /* Skip the allcommands, allkeys and allchannels flags because they'll | |||
617 | * be emitted later as +@all, ~* and &*. */ | |||
618 | if (ACLUserFlags[j].flag == USER_FLAG_ALLKEYS(1<<2) || | |||
619 | ACLUserFlags[j].flag == USER_FLAG_ALLCHANNELS(1<<5) || | |||
620 | ACLUserFlags[j].flag == USER_FLAG_ALLCOMMANDS(1<<3)) continue; | |||
621 | if (u->flags & ACLUserFlags[j].flag) { | |||
622 | res = sdscat(res,ACLUserFlags[j].name); | |||
623 | res = sdscatlen(res," ",1); | |||
624 | } | |||
625 | } | |||
626 | ||||
627 | /* Passwords. */ | |||
628 | listIter li; | |||
629 | listNode *ln; | |||
630 | listRewind(u->passwords,&li); | |||
631 | while((ln = listNext(&li))) { | |||
632 | sds thispass = listNodeValue(ln)((ln)->value); | |||
633 | res = sdscatlen(res,"#",1); | |||
634 | res = sdscatsds(res,thispass); | |||
635 | res = sdscatlen(res," ",1); | |||
636 | } | |||
637 | ||||
638 | /* Key patterns. */ | |||
639 | if (u->flags & USER_FLAG_ALLKEYS(1<<2)) { | |||
640 | res = sdscatlen(res,"~* ",3); | |||
641 | } else { | |||
642 | listRewind(u->patterns,&li); | |||
643 | while((ln = listNext(&li))) { | |||
644 | sds thispat = listNodeValue(ln)((ln)->value); | |||
645 | res = sdscatlen(res,"~",1); | |||
646 | res = sdscatsds(res,thispat); | |||
647 | res = sdscatlen(res," ",1); | |||
648 | } | |||
649 | } | |||
650 | ||||
651 | /* Pub/sub channel patterns. */ | |||
652 | if (u->flags & USER_FLAG_ALLCHANNELS(1<<5)) { | |||
653 | res = sdscatlen(res,"&* ",3); | |||
654 | } else { | |||
655 | listRewind(u->channels,&li); | |||
656 | while((ln = listNext(&li))) { | |||
657 | sds thispat = listNodeValue(ln)((ln)->value); | |||
658 | res = sdscatlen(res,"&",1); | |||
659 | res = sdscatsds(res,thispat); | |||
660 | res = sdscatlen(res," ",1); | |||
661 | } | |||
662 | } | |||
663 | ||||
664 | /* Command rules. */ | |||
665 | sds rules = ACLDescribeUserCommandRules(u); | |||
666 | res = sdscatsds(res,rules); | |||
667 | sdsfree(rules); | |||
668 | return res; | |||
669 | } | |||
670 | ||||
671 | /* Get a command from the original command table, that is not affected | |||
672 | * by the command renaming operations: we base all the ACL work from that | |||
673 | * table, so that ACLs are valid regardless of command renaming. */ | |||
674 | struct redisCommand *ACLLookupCommand(const char *name) { | |||
675 | struct redisCommand *cmd; | |||
676 | sds sdsname = sdsnew(name); | |||
677 | cmd = dictFetchValue(server.orig_commands, sdsname); | |||
678 | sdsfree(sdsname); | |||
679 | return cmd; | |||
680 | } | |||
681 | ||||
682 | /* Flush the array of allowed subcommands for the specified user | |||
683 | * and command ID. */ | |||
684 | void ACLResetSubcommandsForCommand(user *u, unsigned long id) { | |||
685 | if (u->allowed_subcommands && u->allowed_subcommands[id]) { | |||
686 | for (int i = 0; u->allowed_subcommands[id][i]; i++) | |||
687 | sdsfree(u->allowed_subcommands[id][i]); | |||
688 | zfree(u->allowed_subcommands[id]); | |||
689 | u->allowed_subcommands[id] = NULL((void*)0); | |||
690 | } | |||
691 | } | |||
692 | ||||
693 | /* Flush the entire table of subcommands. This is useful on +@all, -@all | |||
694 | * or similar to return back to the minimal memory usage (and checks to do) | |||
695 | * for the user. */ | |||
696 | void ACLResetSubcommands(user *u) { | |||
697 | if (u->allowed_subcommands == NULL((void*)0)) return; | |||
698 | for (int j = 0; j < USER_COMMAND_BITS_COUNT1024; j++) { | |||
699 | if (u->allowed_subcommands[j]) { | |||
700 | for (int i = 0; u->allowed_subcommands[j][i]; i++) | |||
701 | sdsfree(u->allowed_subcommands[j][i]); | |||
702 | zfree(u->allowed_subcommands[j]); | |||
703 | } | |||
704 | } | |||
705 | zfree(u->allowed_subcommands); | |||
706 | u->allowed_subcommands = NULL((void*)0); | |||
707 | } | |||
708 | ||||
709 | /* Add a subcommand to the list of subcommands for the user 'u' and | |||
710 | * the command id specified. */ | |||
711 | void ACLAddAllowedSubcommand(user *u, unsigned long id, const char *sub) { | |||
712 | /* If this is the first subcommand to be configured for | |||
713 | * this user, we have to allocate the subcommands array. */ | |||
714 | if (u->allowed_subcommands == NULL((void*)0)) { | |||
715 | u->allowed_subcommands = zcalloc(USER_COMMAND_BITS_COUNT1024 * | |||
716 | sizeof(sds*)); | |||
717 | } | |||
718 | ||||
719 | /* We also need to enlarge the allocation pointing to the | |||
720 | * null terminated SDS array, to make space for this one. | |||
721 | * To start check the current size, and while we are here | |||
722 | * make sure the subcommand is not already specified inside. */ | |||
723 | long items = 0; | |||
724 | if (u->allowed_subcommands[id]) { | |||
725 | while(u->allowed_subcommands[id][items]) { | |||
726 | /* If it's already here do not add it again. */ | |||
727 | if (!strcasecmp(u->allowed_subcommands[id][items],sub)) return; | |||
728 | items++; | |||
729 | } | |||
730 | } | |||
731 | ||||
732 | /* Now we can make space for the new item (and the null term). */ | |||
733 | items += 2; | |||
734 | u->allowed_subcommands[id] = zrealloc(u->allowed_subcommands[id], | |||
735 | sizeof(sds)*items); | |||
736 | u->allowed_subcommands[id][items-2] = sdsnew(sub); | |||
737 | u->allowed_subcommands[id][items-1] = NULL((void*)0); | |||
738 | } | |||
739 | ||||
740 | /* Set user properties according to the string "op". The following | |||
741 | * is a description of what different strings will do: | |||
742 | * | |||
743 | * on Enable the user: it is possible to authenticate as this user. | |||
744 | * off Disable the user: it's no longer possible to authenticate | |||
745 | * with this user, however the already authenticated connections | |||
746 | * will still work. | |||
747 | * +<command> Allow the execution of that command | |||
748 | * -<command> Disallow the execution of that command | |||
749 | * +@<category> Allow the execution of all the commands in such category | |||
750 | * with valid categories are like @admin, @set, @sortedset, ... | |||
751 | * and so forth, see the full list in the server.c file where | |||
752 | * the Redis command table is described and defined. | |||
753 | * The special category @all means all the commands, but currently | |||
754 | * present in the server, and that will be loaded in the future | |||
755 | * via modules. | |||
756 | * +<command>|subcommand Allow a specific subcommand of an otherwise | |||
757 | * disabled command. Note that this form is not | |||
758 | * allowed as negative like -DEBUG|SEGFAULT, but | |||
759 | * only additive starting with "+". | |||
760 | * allcommands Alias for +@all. Note that it implies the ability to execute | |||
761 | * all the future commands loaded via the modules system. | |||
762 | * nocommands Alias for -@all. | |||
763 | * ~<pattern> Add a pattern of keys that can be mentioned as part of | |||
764 | * commands. For instance ~* allows all the keys. The pattern | |||
765 | * is a glob-style pattern like the one of KEYS. | |||
766 | * It is possible to specify multiple patterns. | |||
767 | * allkeys Alias for ~* | |||
768 | * resetkeys Flush the list of allowed keys patterns. | |||
769 | * &<pattern> Add a pattern of channels that can be mentioned as part of | |||
770 | * Pub/Sub commands. For instance &* allows all the channels. The | |||
771 | * pattern is a glob-style pattern like the one of PSUBSCRIBE. | |||
772 | * It is possible to specify multiple patterns. | |||
773 | * allchannels Alias for &* | |||
774 | * resetchannels Flush the list of allowed keys patterns. | |||
775 | * ><password> Add this password to the list of valid password for the user. | |||
776 | * For example >mypass will add "mypass" to the list. | |||
777 | * This directive clears the "nopass" flag (see later). | |||
778 | * #<hash> Add this password hash to the list of valid hashes for | |||
779 | * the user. This is useful if you have previously computed | |||
780 | * the hash, and don't want to store it in plaintext. | |||
781 | * This directive clears the "nopass" flag (see later). | |||
782 | * <<password> Remove this password from the list of valid passwords. | |||
783 | * !<hash> Remove this hashed password from the list of valid passwords. | |||
784 | * This is useful when you want to remove a password just by | |||
785 | * hash without knowing its plaintext version at all. | |||
786 | * nopass All the set passwords of the user are removed, and the user | |||
787 | * is flagged as requiring no password: it means that every | |||
788 | * password will work against this user. If this directive is | |||
789 | * used for the default user, every new connection will be | |||
790 | * immediately authenticated with the default user without | |||
791 | * any explicit AUTH command required. Note that the "resetpass" | |||
792 | * directive will clear this condition. | |||
793 | * resetpass Flush the list of allowed passwords. Moreover removes the | |||
794 | * "nopass" status. After "resetpass" the user has no associated | |||
795 | * passwords and there is no way to authenticate without adding | |||
796 | * some password (or setting it as "nopass" later). | |||
797 | * reset Performs the following actions: resetpass, resetkeys, off, | |||
798 | * -@all. The user returns to the same state it has immediately | |||
799 | * after its creation. | |||
800 | * | |||
801 | * The 'op' string must be null terminated. The 'oplen' argument should | |||
802 | * specify the length of the 'op' string in case the caller requires to pass | |||
803 | * binary data (for instance the >password form may use a binary password). | |||
804 | * Otherwise the field can be set to -1 and the function will use strlen() | |||
805 | * to determine the length. | |||
806 | * | |||
807 | * The function returns C_OK if the action to perform was understood because | |||
808 | * the 'op' string made sense. Otherwise C_ERR is returned if the operation | |||
809 | * is unknown or has some syntax error. | |||
810 | * | |||
811 | * When an error is returned, errno is set to the following values: | |||
812 | * | |||
813 | * EINVAL: The specified opcode is not understood or the key/channel pattern is | |||
814 | * invalid (contains non allowed characters). | |||
815 | * ENOENT: The command name or command category provided with + or - is not | |||
816 | * known. | |||
817 | * EEXIST: You are adding a key pattern after "*" was already added. This is | |||
818 | * almost surely an error on the user side. | |||
819 | * EISDIR: You are adding a channel pattern after "*" was already added. This is | |||
820 | * almost surely an error on the user side. | |||
821 | * ENODEV: The password you are trying to remove from the user does not exist. | |||
822 | * EBADMSG: The hash you are trying to add is not a valid hash. | |||
823 | */ | |||
824 | int ACLSetUser(user *u, const char *op, ssize_t oplen) { | |||
825 | if (oplen == -1) oplen = strlen(op); | |||
826 | if (oplen == 0) return C_OK0; /* Empty string is a no-operation. */ | |||
827 | if (!strcasecmp(op,"on")) { | |||
828 | u->flags |= USER_FLAG_ENABLED(1<<0); | |||
829 | u->flags &= ~USER_FLAG_DISABLED(1<<1); | |||
830 | } else if (!strcasecmp(op,"off")) { | |||
831 | u->flags |= USER_FLAG_DISABLED(1<<1); | |||
832 | u->flags &= ~USER_FLAG_ENABLED(1<<0); | |||
833 | } else if (!strcasecmp(op,"skip-sanitize-payload")) { | |||
834 | u->flags |= USER_FLAG_SANITIZE_PAYLOAD_SKIP(1<<7); | |||
835 | u->flags &= ~USER_FLAG_SANITIZE_PAYLOAD(1<<6); | |||
836 | } else if (!strcasecmp(op,"sanitize-payload")) { | |||
837 | u->flags &= ~USER_FLAG_SANITIZE_PAYLOAD_SKIP(1<<7); | |||
838 | u->flags |= USER_FLAG_SANITIZE_PAYLOAD(1<<6); | |||
839 | } else if (!strcasecmp(op,"allkeys") || | |||
840 | !strcasecmp(op,"~*")) | |||
841 | { | |||
842 | u->flags |= USER_FLAG_ALLKEYS(1<<2); | |||
843 | listEmpty(u->patterns); | |||
844 | } else if (!strcasecmp(op,"resetkeys")) { | |||
845 | u->flags &= ~USER_FLAG_ALLKEYS(1<<2); | |||
846 | listEmpty(u->patterns); | |||
847 | } else if (!strcasecmp(op,"allchannels") || | |||
848 | !strcasecmp(op,"&*")) | |||
849 | { | |||
850 | u->flags |= USER_FLAG_ALLCHANNELS(1<<5); | |||
851 | listEmpty(u->channels); | |||
852 | } else if (!strcasecmp(op,"resetchannels")) { | |||
853 | u->flags &= ~USER_FLAG_ALLCHANNELS(1<<5); | |||
854 | listEmpty(u->channels); | |||
855 | } else if (!strcasecmp(op,"allcommands") || | |||
856 | !strcasecmp(op,"+@all")) | |||
857 | { | |||
858 | memset(u->allowed_commands,255,sizeof(u->allowed_commands)); | |||
859 | u->flags |= USER_FLAG_ALLCOMMANDS(1<<3); | |||
860 | ACLResetSubcommands(u); | |||
861 | } else if (!strcasecmp(op,"nocommands") || | |||
862 | !strcasecmp(op,"-@all")) | |||
863 | { | |||
864 | memset(u->allowed_commands,0,sizeof(u->allowed_commands)); | |||
865 | u->flags &= ~USER_FLAG_ALLCOMMANDS(1<<3); | |||
866 | ACLResetSubcommands(u); | |||
867 | } else if (!strcasecmp(op,"nopass")) { | |||
868 | u->flags |= USER_FLAG_NOPASS(1<<4); | |||
869 | listEmpty(u->passwords); | |||
870 | } else if (!strcasecmp(op,"resetpass")) { | |||
871 | u->flags &= ~USER_FLAG_NOPASS(1<<4); | |||
872 | listEmpty(u->passwords); | |||
873 | } else if (op[0] == '>' || op[0] == '#') { | |||
874 | sds newpass; | |||
875 | if (op[0] == '>') { | |||
876 | newpass = ACLHashPassword((unsigned char*)op+1,oplen-1); | |||
877 | } else { | |||
878 | if (ACLCheckPasswordHash((unsigned char*)op+1,oplen-1) == C_ERR-1) { | |||
879 | errno(*__errno_location ()) = EBADMSG74; | |||
880 | return C_ERR-1; | |||
881 | } | |||
882 | newpass = sdsnewlen(op+1,oplen-1); | |||
883 | } | |||
884 | ||||
885 | listNode *ln = listSearchKey(u->passwords,newpass); | |||
886 | /* Avoid re-adding the same password multiple times. */ | |||
887 | if (ln == NULL((void*)0)) | |||
888 | listAddNodeTail(u->passwords,newpass); | |||
889 | else | |||
890 | sdsfree(newpass); | |||
891 | u->flags &= ~USER_FLAG_NOPASS(1<<4); | |||
892 | } else if (op[0] == '<' || op[0] == '!') { | |||
893 | sds delpass; | |||
894 | if (op[0] == '<') { | |||
895 | delpass = ACLHashPassword((unsigned char*)op+1,oplen-1); | |||
896 | } else { | |||
897 | if (ACLCheckPasswordHash((unsigned char*)op+1,oplen-1) == C_ERR-1) { | |||
898 | errno(*__errno_location ()) = EBADMSG74; | |||
899 | return C_ERR-1; | |||
900 | } | |||
901 | delpass = sdsnewlen(op+1,oplen-1); | |||
902 | } | |||
903 | listNode *ln = listSearchKey(u->passwords,delpass); | |||
904 | sdsfree(delpass); | |||
905 | if (ln) { | |||
906 | listDelNode(u->passwords,ln); | |||
907 | } else { | |||
908 | errno(*__errno_location ()) = ENODEV19; | |||
909 | return C_ERR-1; | |||
910 | } | |||
911 | } else if (op[0] == '~') { | |||
912 | if (u->flags & USER_FLAG_ALLKEYS(1<<2)) { | |||
913 | errno(*__errno_location ()) = EEXIST17; | |||
914 | return C_ERR-1; | |||
915 | } | |||
916 | if (ACLStringHasSpaces(op+1,oplen-1)) { | |||
917 | errno(*__errno_location ()) = EINVAL22; | |||
918 | return C_ERR-1; | |||
919 | } | |||
920 | sds newpat = sdsnewlen(op+1,oplen-1); | |||
921 | listNode *ln = listSearchKey(u->patterns,newpat); | |||
922 | /* Avoid re-adding the same key pattern multiple times. */ | |||
923 | if (ln == NULL((void*)0)) | |||
924 | listAddNodeTail(u->patterns,newpat); | |||
925 | else | |||
926 | sdsfree(newpat); | |||
927 | u->flags &= ~USER_FLAG_ALLKEYS(1<<2); | |||
928 | } else if (op[0] == '&') { | |||
929 | if (u->flags & USER_FLAG_ALLCHANNELS(1<<5)) { | |||
930 | errno(*__errno_location ()) = EISDIR21; | |||
931 | return C_ERR-1; | |||
932 | } | |||
933 | if (ACLStringHasSpaces(op+1,oplen-1)) { | |||
934 | errno(*__errno_location ()) = EINVAL22; | |||
935 | return C_ERR-1; | |||
936 | } | |||
937 | sds newpat = sdsnewlen(op+1,oplen-1); | |||
938 | listNode *ln = listSearchKey(u->channels,newpat); | |||
939 | /* Avoid re-adding the same channel pattern multiple times. */ | |||
940 | if (ln == NULL((void*)0)) | |||
941 | listAddNodeTail(u->channels,newpat); | |||
942 | else | |||
943 | sdsfree(newpat); | |||
944 | u->flags &= ~USER_FLAG_ALLCHANNELS(1<<5); | |||
945 | } else if (op[0] == '+' && op[1] != '@') { | |||
946 | if (strchr(op,'|') == NULL((void*)0)) { | |||
947 | if (ACLLookupCommand(op+1) == NULL((void*)0)) { | |||
948 | errno(*__errno_location ()) = ENOENT2; | |||
949 | return C_ERR-1; | |||
950 | } | |||
951 | unsigned long id = ACLGetCommandID(op+1); | |||
952 | ACLSetUserCommandBit(u,id,1); | |||
953 | ACLResetSubcommandsForCommand(u,id); | |||
954 | } else { | |||
955 | /* Split the command and subcommand parts. */ | |||
956 | char *copy = zstrdup(op+1); | |||
957 | char *sub = strchr(copy,'|'); | |||
958 | sub[0] = '\0'; | |||
959 | sub++; | |||
960 | ||||
961 | /* Check if the command exists. We can't check the | |||
962 | * subcommand to see if it is valid. */ | |||
963 | if (ACLLookupCommand(copy) == NULL((void*)0)) { | |||
964 | zfree(copy); | |||
965 | errno(*__errno_location ()) = ENOENT2; | |||
966 | return C_ERR-1; | |||
967 | } | |||
968 | ||||
969 | /* The subcommand cannot be empty, so things like DEBUG| | |||
970 | * are syntax errors of course. */ | |||
971 | if (strlen(sub) == 0) { | |||
972 | zfree(copy); | |||
973 | errno(*__errno_location ()) = EINVAL22; | |||
974 | return C_ERR-1; | |||
975 | } | |||
976 | ||||
977 | unsigned long id = ACLGetCommandID(copy); | |||
978 | /* Add the subcommand to the list of valid ones, if the command is not set. */ | |||
979 | if (ACLGetUserCommandBit(u,id) == 0) { | |||
980 | ACLAddAllowedSubcommand(u,id,sub); | |||
981 | } | |||
982 | ||||
983 | zfree(copy); | |||
984 | } | |||
985 | } else if (op[0] == '-' && op[1] != '@') { | |||
986 | if (ACLLookupCommand(op+1) == NULL((void*)0)) { | |||
987 | errno(*__errno_location ()) = ENOENT2; | |||
988 | return C_ERR-1; | |||
989 | } | |||
990 | unsigned long id = ACLGetCommandID(op+1); | |||
991 | ACLSetUserCommandBit(u,id,0); | |||
992 | ACLResetSubcommandsForCommand(u,id); | |||
993 | } else if ((op[0] == '+' || op[0] == '-') && op[1] == '@') { | |||
994 | int bitval = op[0] == '+' ? 1 : 0; | |||
995 | if (ACLSetUserCommandBitsForCategory(u,op+2,bitval) == C_ERR-1) { | |||
996 | errno(*__errno_location ()) = ENOENT2; | |||
997 | return C_ERR-1; | |||
998 | } | |||
999 | } else if (!strcasecmp(op,"reset")) { | |||
1000 | serverAssert(ACLSetUser(u,"resetpass",-1) == C_OK)((ACLSetUser(u,"resetpass",-1) == 0)?(void)0 : (_serverAssert ("ACLSetUser(u,\"resetpass\",-1) == C_OK","acl.c",1000),__builtin_unreachable ())); | |||
1001 | serverAssert(ACLSetUser(u,"resetkeys",-1) == C_OK)((ACLSetUser(u,"resetkeys",-1) == 0)?(void)0 : (_serverAssert ("ACLSetUser(u,\"resetkeys\",-1) == C_OK","acl.c",1001),__builtin_unreachable ())); | |||
1002 | serverAssert(ACLSetUser(u,"resetchannels",-1) == C_OK)((ACLSetUser(u,"resetchannels",-1) == 0)?(void)0 : (_serverAssert ("ACLSetUser(u,\"resetchannels\",-1) == C_OK","acl.c",1002),__builtin_unreachable ())); | |||
1003 | serverAssert(ACLSetUser(u,"off",-1) == C_OK)((ACLSetUser(u,"off",-1) == 0)?(void)0 : (_serverAssert("ACLSetUser(u,\"off\",-1) == C_OK" ,"acl.c",1003),__builtin_unreachable())); | |||
1004 | serverAssert(ACLSetUser(u,"sanitize-payload",-1) == C_OK)((ACLSetUser(u,"sanitize-payload",-1) == 0)?(void)0 : (_serverAssert ("ACLSetUser(u,\"sanitize-payload\",-1) == C_OK","acl.c",1004 ),__builtin_unreachable())); | |||
1005 | serverAssert(ACLSetUser(u,"-@all",-1) == C_OK)((ACLSetUser(u,"-@all",-1) == 0)?(void)0 : (_serverAssert("ACLSetUser(u,\"-@all\",-1) == C_OK" ,"acl.c",1005),__builtin_unreachable())); | |||
1006 | } else { | |||
1007 | errno(*__errno_location ()) = EINVAL22; | |||
1008 | return C_ERR-1; | |||
1009 | } | |||
1010 | return C_OK0; | |||
1011 | } | |||
1012 | ||||
1013 | /* Return a description of the error that occurred in ACLSetUser() according to | |||
1014 | * the errno value set by the function on error. */ | |||
1015 | const char *ACLSetUserStringError(void) { | |||
1016 | const char *errmsg = "Wrong format"; | |||
1017 | if (errno(*__errno_location ()) == ENOENT2) | |||
1018 | errmsg = "Unknown command or category name in ACL"; | |||
1019 | else if (errno(*__errno_location ()) == EINVAL22) | |||
1020 | errmsg = "Syntax error"; | |||
1021 | else if (errno(*__errno_location ()) == EEXIST17) | |||
1022 | errmsg = "Adding a pattern after the * pattern (or the " | |||
1023 | "'allkeys' flag) is not valid and does not have any " | |||
1024 | "effect. Try 'resetkeys' to start with an empty " | |||
1025 | "list of patterns"; | |||
1026 | else if (errno(*__errno_location ()) == EISDIR21) | |||
1027 | errmsg = "Adding a pattern after the * pattern (or the " | |||
1028 | "'allchannels' flag) is not valid and does not have any " | |||
1029 | "effect. Try 'resetchannels' to start with an empty " | |||
1030 | "list of channels"; | |||
1031 | else if (errno(*__errno_location ()) == ENODEV19) | |||
1032 | errmsg = "The password you are trying to remove from the user does " | |||
1033 | "not exist"; | |||
1034 | else if (errno(*__errno_location ()) == EBADMSG74) | |||
1035 | errmsg = "The password hash must be exactly 64 characters and contain " | |||
1036 | "only lowercase hexadecimal characters"; | |||
1037 | return errmsg; | |||
1038 | } | |||
1039 | ||||
1040 | /* Initialize the default user, that will always exist for all the process | |||
1041 | * lifetime. */ | |||
1042 | void ACLInitDefaultUser(void) { | |||
1043 | DefaultUser = ACLCreateUser("default",7); | |||
1044 | ACLSetUser(DefaultUser,"+@all",-1); | |||
1045 | ACLSetUser(DefaultUser,"~*",-1); | |||
1046 | ACLSetUser(DefaultUser,"&*",-1); | |||
1047 | ACLSetUser(DefaultUser,"on",-1); | |||
1048 | ACLSetUser(DefaultUser,"nopass",-1); | |||
1049 | } | |||
1050 | ||||
1051 | /* Initialization of the ACL subsystem. */ | |||
1052 | void ACLInit(void) { | |||
1053 | Users = raxNew(); | |||
1054 | UsersToLoad = listCreate(); | |||
1055 | ACLLog = listCreate(); | |||
1056 | ACLInitDefaultUser(); | |||
1057 | } | |||
1058 | ||||
1059 | /* Check the username and password pair and return C_OK if they are valid, | |||
1060 | * otherwise C_ERR is returned and errno is set to: | |||
1061 | * | |||
1062 | * EINVAL: if the username-password do not match. | |||
1063 | * ENONENT: if the specified user does not exist at all. | |||
1064 | */ | |||
1065 | int ACLCheckUserCredentials(robj *username, robj *password) { | |||
1066 | user *u = ACLGetUserByName(username->ptr,sdslen(username->ptr)); | |||
1067 | if (u == NULL((void*)0)) { | |||
1068 | errno(*__errno_location ()) = ENOENT2; | |||
1069 | return C_ERR-1; | |||
1070 | } | |||
1071 | ||||
1072 | /* Disabled users can't login. */ | |||
1073 | if (u->flags & USER_FLAG_DISABLED(1<<1)) { | |||
1074 | errno(*__errno_location ()) = EINVAL22; | |||
1075 | return C_ERR-1; | |||
1076 | } | |||
1077 | ||||
1078 | /* If the user is configured to don't require any password, we | |||
1079 | * are already fine here. */ | |||
1080 | if (u->flags & USER_FLAG_NOPASS(1<<4)) return C_OK0; | |||
1081 | ||||
1082 | /* Check all the user passwords for at least one to match. */ | |||
1083 | listIter li; | |||
1084 | listNode *ln; | |||
1085 | listRewind(u->passwords,&li); | |||
1086 | sds hashed = ACLHashPassword(password->ptr,sdslen(password->ptr)); | |||
1087 | while((ln = listNext(&li))) { | |||
1088 | sds thispass = listNodeValue(ln)((ln)->value); | |||
1089 | if (!time_independent_strcmp(hashed, thispass)) { | |||
1090 | sdsfree(hashed); | |||
1091 | return C_OK0; | |||
1092 | } | |||
1093 | } | |||
1094 | sdsfree(hashed); | |||
1095 | ||||
1096 | /* If we reached this point, no password matched. */ | |||
1097 | errno(*__errno_location ()) = EINVAL22; | |||
1098 | return C_ERR-1; | |||
1099 | } | |||
1100 | ||||
1101 | /* This is like ACLCheckUserCredentials(), however if the user/pass | |||
1102 | * are correct, the connection is put in authenticated state and the | |||
1103 | * connection user reference is populated. | |||
1104 | * | |||
1105 | * The return value is C_OK or C_ERR with the same meaning as | |||
1106 | * ACLCheckUserCredentials(). */ | |||
1107 | int ACLAuthenticateUser(client *c, robj *username, robj *password) { | |||
1108 | if (ACLCheckUserCredentials(username,password) == C_OK0) { | |||
1109 | c->authenticated = 1; | |||
1110 | c->user = ACLGetUserByName(username->ptr,sdslen(username->ptr)); | |||
1111 | moduleNotifyUserChanged(c); | |||
1112 | return C_OK0; | |||
1113 | } else { | |||
1114 | addACLLogEntry(c,ACL_DENIED_AUTH3,0,username->ptr); | |||
1115 | return C_ERR-1; | |||
1116 | } | |||
1117 | } | |||
1118 | ||||
1119 | /* For ACL purposes, every user has a bitmap with the commands that such | |||
1120 | * user is allowed to execute. In order to populate the bitmap, every command | |||
1121 | * should have an assigned ID (that is used to index the bitmap). This function | |||
1122 | * creates such an ID: it uses sequential IDs, reusing the same ID for the same | |||
1123 | * command name, so that a command retains the same ID in case of modules that | |||
1124 | * are unloaded and later reloaded. */ | |||
1125 | unsigned long ACLGetCommandID(const char *cmdname) { | |||
1126 | ||||
1127 | sds lowername = sdsnew(cmdname); | |||
1128 | sdstolower(lowername); | |||
1129 | if (commandId == NULL((void*)0)) commandId = raxNew(); | |||
1130 | void *id = raxFind(commandId,(unsigned char*)lowername,sdslen(lowername)); | |||
1131 | if (id != raxNotFound) { | |||
1132 | sdsfree(lowername); | |||
1133 | return (unsigned long)id; | |||
1134 | } | |||
1135 | raxInsert(commandId,(unsigned char*)lowername,strlen(lowername), | |||
1136 | (void*)nextid,NULL((void*)0)); | |||
1137 | sdsfree(lowername); | |||
1138 | unsigned long thisid = nextid; | |||
1139 | nextid++; | |||
1140 | ||||
1141 | /* We never assign the last bit in the user commands bitmap structure, | |||
1142 | * this way we can later check if this bit is set, understanding if the | |||
1143 | * current ACL for the user was created starting with a +@all to add all | |||
1144 | * the possible commands and just subtracting other single commands or | |||
1145 | * categories, or if, instead, the ACL was created just adding commands | |||
1146 | * and command categories from scratch, not allowing future commands by | |||
1147 | * default (loaded via modules). This is useful when rewriting the ACLs | |||
1148 | * with ACL SAVE. */ | |||
1149 | if (nextid == USER_COMMAND_BITS_COUNT1024-1) nextid++; | |||
1150 | return thisid; | |||
1151 | } | |||
1152 | ||||
1153 | /* Clear command id table and reset nextid to 0. */ | |||
1154 | void ACLClearCommandID(void) { | |||
1155 | if (commandId) raxFree(commandId); | |||
1156 | commandId = NULL((void*)0); | |||
1157 | nextid = 0; | |||
1158 | } | |||
1159 | ||||
1160 | /* Return an username by its name, or NULL if the user does not exist. */ | |||
1161 | user *ACLGetUserByName(const char *name, size_t namelen) { | |||
1162 | void *myuser = raxFind(Users,(unsigned char*)name,namelen); | |||
1163 | if (myuser == raxNotFound) return NULL((void*)0); | |||
1164 | return myuser; | |||
1165 | } | |||
1166 | ||||
1167 | /* Check if the command is ready to be executed in the client 'c', already | |||
1168 | * referenced by c->cmd, and can be executed by this client according to the | |||
1169 | * ACLs associated to the client user c->user. | |||
1170 | * | |||
1171 | * If the user can execute the command ACL_OK is returned, otherwise | |||
1172 | * ACL_DENIED_CMD or ACL_DENIED_KEY is returned: the first in case the | |||
1173 | * command cannot be executed because the user is not allowed to run such | |||
1174 | * command, the second if the command is denied because the user is trying | |||
1175 | * to access keys that are not among the specified patterns. */ | |||
1176 | int ACLCheckCommandPerm(client *c, int *keyidxptr) { | |||
1177 | user *u = c->user; | |||
1178 | uint64_t id = c->cmd->id; | |||
1179 | ||||
1180 | /* If there is no associated user, the connection can run anything. */ | |||
1181 | if (u == NULL((void*)0)) return ACL_OK0; | |||
1182 | ||||
1183 | /* Check if the user can execute this command. */ | |||
1184 | if (!(u->flags & USER_FLAG_ALLCOMMANDS(1<<3)) && | |||
1185 | c->cmd->proc != authCommand) | |||
1186 | { | |||
1187 | /* If the bit is not set we have to check further, in case the | |||
1188 | * command is allowed just with that specific subcommand. */ | |||
1189 | if (ACLGetUserCommandBit(u,id) == 0) { | |||
1190 | /* Check if the subcommand matches. */ | |||
1191 | if (c->argc < 2 || | |||
1192 | u->allowed_subcommands == NULL((void*)0) || | |||
1193 | u->allowed_subcommands[id] == NULL((void*)0)) | |||
1194 | { | |||
1195 | return ACL_DENIED_CMD1; | |||
1196 | } | |||
1197 | ||||
1198 | long subid = 0; | |||
1199 | while (1) { | |||
1200 | if (u->allowed_subcommands[id][subid] == NULL((void*)0)) | |||
1201 | return ACL_DENIED_CMD1; | |||
1202 | if (!strcasecmp(c->argv[1]->ptr, | |||
1203 | u->allowed_subcommands[id][subid])) | |||
1204 | break; /* Subcommand match found. Stop here. */ | |||
1205 | subid++; | |||
1206 | } | |||
1207 | } | |||
1208 | } | |||
1209 | ||||
1210 | /* Check if the user can execute commands explicitly touching the keys | |||
1211 | * mentioned in the command arguments. */ | |||
1212 | if (!(c->user->flags & USER_FLAG_ALLKEYS(1<<2)) && | |||
1213 | (c->cmd->getkeys_proc || c->cmd->firstkey)) | |||
1214 | { | |||
1215 | getKeysResult result = GETKEYS_RESULT_INIT{ {0}, ((void*)0), 0, 256 }; | |||
1216 | int numkeys = getKeysFromCommand(c->cmd,c->argv,c->argc,&result); | |||
1217 | int *keyidx = result.keys; | |||
1218 | for (int j = 0; j < numkeys; j++) { | |||
1219 | listIter li; | |||
1220 | listNode *ln; | |||
1221 | listRewind(u->patterns,&li); | |||
1222 | ||||
1223 | /* Test this key against every pattern. */ | |||
1224 | int match = 0; | |||
1225 | while((ln = listNext(&li))) { | |||
1226 | sds pattern = listNodeValue(ln)((ln)->value); | |||
1227 | size_t plen = sdslen(pattern); | |||
1228 | int idx = keyidx[j]; | |||
1229 | if (stringmatchlen(pattern,plen,c->argv[idx]->ptr, | |||
1230 | sdslen(c->argv[idx]->ptr),0)) | |||
1231 | { | |||
1232 | match = 1; | |||
1233 | break; | |||
1234 | } | |||
1235 | } | |||
1236 | if (!match) { | |||
1237 | if (keyidxptr) *keyidxptr = keyidx[j]; | |||
1238 | getKeysFreeResult(&result); | |||
1239 | return ACL_DENIED_KEY2; | |||
1240 | } | |||
1241 | } | |||
1242 | getKeysFreeResult(&result); | |||
1243 | } | |||
1244 | ||||
1245 | /* If we survived all the above checks, the user can execute the | |||
1246 | * command. */ | |||
1247 | return ACL_OK0; | |||
1248 | } | |||
1249 | ||||
1250 | /* Check if the provided channel is whitelisted by the given allowed channels | |||
1251 | * list. Glob-style pattern matching is employed, unless the literal flag is | |||
1252 | * set. Returns ACL_OK if access is granted or ACL_DENIED_CHANNEL otherwise. */ | |||
1253 | int ACLCheckPubsubChannelPerm(sds channel, list *allowed, int literal) { | |||
1254 | listIter li; | |||
1255 | listNode *ln; | |||
1256 | size_t clen = sdslen(channel); | |||
1257 | int match = 0; | |||
1258 | ||||
1259 | listRewind(allowed,&li); | |||
1260 | while((ln = listNext(&li))) { | |||
1261 | sds pattern = listNodeValue(ln)((ln)->value); | |||
1262 | size_t plen = sdslen(pattern); | |||
1263 | ||||
1264 | if ((literal && !sdscmp(pattern,channel)) || | |||
1265 | (!literal && stringmatchlen(pattern,plen,channel,clen,0))) | |||
1266 | { | |||
1267 | match = 1; | |||
1268 | break; | |||
1269 | } | |||
1270 | } | |||
1271 | if (!match) { | |||
1272 | return ACL_DENIED_CHANNEL4; | |||
1273 | } | |||
1274 | return ACL_OK0; | |||
1275 | } | |||
1276 | ||||
1277 | /* Check if the user's existing pub/sub clients violate the ACL pub/sub | |||
1278 | * permissions specified via the upcoming argument, and kill them if so. */ | |||
1279 | void ACLKillPubsubClientsIfNeeded(user *u, list *upcoming) { | |||
1280 | listIter li, lpi; | |||
1281 | listNode *ln, *lpn; | |||
1282 | robj *o; | |||
1283 | int kill = 0; | |||
1284 | ||||
1285 | /* Nothing to kill when the upcoming are a literal super set of the original | |||
1286 | * permissions. */ | |||
1287 | listRewind(u->channels,&li); | |||
1288 | while (!kill && ((ln = listNext(&li)) != NULL((void*)0))) { | |||
1289 | sds pattern = listNodeValue(ln)((ln)->value); | |||
1290 | kill = (ACLCheckPubsubChannelPerm(pattern,upcoming,1) == | |||
1291 | ACL_DENIED_CHANNEL4); | |||
1292 | } | |||
1293 | if (!kill) return; | |||
1294 | ||||
1295 | /* Scan all connected clients to find the user's pub/subs. */ | |||
1296 | listRewind(server.clients,&li); | |||
1297 | while ((ln = listNext(&li)) != NULL((void*)0)) { | |||
1298 | client *c = listNodeValue(ln)((ln)->value); | |||
1299 | kill = 0; | |||
1300 | ||||
1301 | if (c->user == u && getClientType(c) == CLIENT_TYPE_PUBSUB2) { | |||
1302 | /* Check for pattern violations. */ | |||
1303 | listRewind(c->pubsub_patterns,&lpi); | |||
1304 | while (!kill && ((lpn = listNext(&lpi)) != NULL((void*)0))) { | |||
1305 | o = lpn->value; | |||
1306 | kill = (ACLCheckPubsubChannelPerm(o->ptr,upcoming,1) == | |||
1307 | ACL_DENIED_CHANNEL4); | |||
1308 | } | |||
1309 | /* Check for channel violations. */ | |||
1310 | if (!kill) { | |||
1311 | dictIterator *di = dictGetIterator(c->pubsub_channels); | |||
1312 | dictEntry *de; | |||
1313 | while (!kill && ((de = dictNext(di)) != NULL((void*)0))) { | |||
1314 | o = dictGetKey(de)((de)->key); | |||
1315 | kill = (ACLCheckPubsubChannelPerm(o->ptr,upcoming,0) == | |||
1316 | ACL_DENIED_CHANNEL4); | |||
1317 | } | |||
1318 | dictReleaseIterator(di); | |||
1319 | } | |||
1320 | ||||
1321 | /* Kill it. */ | |||
1322 | if (kill) { | |||
1323 | freeClient(c); | |||
1324 | } | |||
1325 | } | |||
1326 | } | |||
1327 | } | |||
1328 | ||||
1329 | /* Check if the pub/sub channels of the command, that's ready to be executed in | |||
1330 | * the client 'c', can be executed by this client according to the ACLs channels | |||
1331 | * associated to the client user c->user. | |||
1332 | * | |||
1333 | * idx and count are the index and count of channel arguments from the | |||
1334 | * command. The literal argument controls whether the user's ACL channels are | |||
1335 | * evaluated as literal values or matched as glob-like patterns. | |||
1336 | * | |||
1337 | * If the user can execute the command ACL_OK is returned, otherwise | |||
1338 | * ACL_DENIED_CHANNEL. */ | |||
1339 | int ACLCheckPubsubPerm(client *c, int idx, int count, int literal, int *idxptr) { | |||
1340 | user *u = c->user; | |||
1341 | ||||
1342 | /* If there is no associated user, the connection can run anything. */ | |||
1343 | if (u == NULL((void*)0)) return ACL_OK0; | |||
1344 | ||||
1345 | /* Check if the user can access the channels mentioned in the command's | |||
1346 | * arguments. */ | |||
1347 | if (!(c->user->flags & USER_FLAG_ALLCHANNELS(1<<5))) { | |||
1348 | for (int j = idx; j < idx+count; j++) { | |||
1349 | if (ACLCheckPubsubChannelPerm(c->argv[j]->ptr,u->channels,literal) | |||
1350 | != ACL_OK0) { | |||
1351 | if (idxptr) *idxptr = j; | |||
1352 | return ACL_DENIED_CHANNEL4; | |||
1353 | } | |||
1354 | } | |||
1355 | } | |||
1356 | ||||
1357 | /* If we survived all the above checks, the user can execute the | |||
1358 | * command. */ | |||
1359 | return ACL_OK0; | |||
1360 | ||||
1361 | } | |||
1362 | ||||
1363 | /* ============================================================================= | |||
1364 | * ACL loading / saving functions | |||
1365 | * ==========================================================================*/ | |||
1366 | ||||
1367 | /* Given an argument vector describing a user in the form: | |||
1368 | * | |||
1369 | * user <username> ... ACL rules and flags ... | |||
1370 | * | |||
1371 | * this function validates, and if the syntax is valid, appends | |||
1372 | * the user definition to a list for later loading. | |||
1373 | * | |||
1374 | * The rules are tested for validity and if there obvious syntax errors | |||
1375 | * the function returns C_ERR and does nothing, otherwise C_OK is returned | |||
1376 | * and the user is appended to the list. | |||
1377 | * | |||
1378 | * Note that this function cannot stop in case of commands that are not found | |||
1379 | * and, in that case, the error will be emitted later, because certain | |||
1380 | * commands may be defined later once modules are loaded. | |||
1381 | * | |||
1382 | * When an error is detected and C_ERR is returned, the function populates | |||
1383 | * by reference (if not set to NULL) the argc_err argument with the index | |||
1384 | * of the argv vector that caused the error. */ | |||
1385 | int ACLAppendUserForLoading(sds *argv, int argc, int *argc_err) { | |||
1386 | if (argc < 2 || strcasecmp(argv[0],"user")) { | |||
1387 | if (argc_err) *argc_err = 0; | |||
1388 | return C_ERR-1; | |||
1389 | } | |||
1390 | ||||
1391 | /* Try to apply the user rules in a fake user to see if they | |||
1392 | * are actually valid. */ | |||
1393 | user *fakeuser = ACLCreateUnlinkedUser(); | |||
1394 | ||||
1395 | for (int j = 2; j < argc; j++) { | |||
1396 | if (ACLSetUser(fakeuser,argv[j],sdslen(argv[j])) == C_ERR-1) { | |||
1397 | if (errno(*__errno_location ()) != ENOENT2) { | |||
1398 | ACLFreeUser(fakeuser); | |||
1399 | if (argc_err) *argc_err = j; | |||
1400 | return C_ERR-1; | |||
1401 | } | |||
1402 | } | |||
1403 | } | |||
1404 | ||||
1405 | /* Rules look valid, let's append the user to the list. */ | |||
1406 | sds *copy = zmalloc(sizeof(sds)*argc); | |||
1407 | for (int j = 1; j < argc; j++) copy[j-1] = sdsdup(argv[j]); | |||
1408 | copy[argc-1] = NULL((void*)0); | |||
1409 | listAddNodeTail(UsersToLoad,copy); | |||
1410 | ACLFreeUser(fakeuser); | |||
1411 | return C_OK0; | |||
1412 | } | |||
1413 | ||||
1414 | /* This function will load the configured users appended to the server | |||
1415 | * configuration via ACLAppendUserForLoading(). On loading errors it will | |||
1416 | * log an error and return C_ERR, otherwise C_OK will be returned. */ | |||
1417 | int ACLLoadConfiguredUsers(void) { | |||
1418 | listIter li; | |||
1419 | listNode *ln; | |||
1420 | listRewind(UsersToLoad,&li); | |||
1421 | while ((ln = listNext(&li)) != NULL((void*)0)) { | |||
1422 | sds *aclrules = listNodeValue(ln)((ln)->value); | |||
1423 | sds username = aclrules[0]; | |||
1424 | ||||
1425 | if (ACLStringHasSpaces(aclrules[0],sdslen(aclrules[0]))) { | |||
1426 | serverLog(LL_WARNING3,"Spaces not allowed in ACL usernames"); | |||
1427 | return C_ERR-1; | |||
1428 | } | |||
1429 | ||||
1430 | user *u = ACLCreateUser(username,sdslen(username)); | |||
1431 | if (!u) { | |||
1432 | u = ACLGetUserByName(username,sdslen(username)); | |||
1433 | serverAssert(u != NULL)((u != ((void*)0))?(void)0 : (_serverAssert("u != NULL","acl.c" ,1433),__builtin_unreachable())); | |||
1434 | ACLSetUser(u,"reset",-1); | |||
1435 | } | |||
1436 | ||||
1437 | /* Load every rule defined for this user. */ | |||
1438 | for (int j = 1; aclrules[j]; j++) { | |||
1439 | if (ACLSetUser(u,aclrules[j],sdslen(aclrules[j])) != C_OK0) { | |||
1440 | const char *errmsg = ACLSetUserStringError(); | |||
1441 | serverLog(LL_WARNING3,"Error loading ACL rule '%s' for " | |||
1442 | "the user named '%s': %s", | |||
1443 | aclrules[j],aclrules[0],errmsg); | |||
1444 | return C_ERR-1; | |||
1445 | } | |||
1446 | } | |||
1447 | ||||
1448 | /* Having a disabled user in the configuration may be an error, | |||
1449 | * warn about it without returning any error to the caller. */ | |||
1450 | if (u->flags & USER_FLAG_DISABLED(1<<1)) { | |||
1451 | serverLog(LL_NOTICE2, "The user '%s' is disabled (there is no " | |||
1452 | "'on' modifier in the user description). Make " | |||
1453 | "sure this is not a configuration error.", | |||
1454 | aclrules[0]); | |||
1455 | } | |||
1456 | } | |||
1457 | return C_OK0; | |||
1458 | } | |||
1459 | ||||
1460 | /* This function loads the ACL from the specified filename: every line | |||
1461 | * is validated and should be either empty or in the format used to specify | |||
1462 | * users in the redis.conf configuration or in the ACL file, that is: | |||
1463 | * | |||
1464 | * user <username> ... rules ... | |||
1465 | * | |||
1466 | * Note that this function considers comments starting with '#' as errors | |||
1467 | * because the ACL file is meant to be rewritten, and comments would be | |||
1468 | * lost after the rewrite. Yet empty lines are allowed to avoid being too | |||
1469 | * strict. | |||
1470 | * | |||
1471 | * One important part of implementing ACL LOAD, that uses this function, is | |||
1472 | * to avoid ending with broken rules if the ACL file is invalid for some | |||
1473 | * reason, so the function will attempt to validate the rules before loading | |||
1474 | * each user. For every line that will be found broken the function will | |||
1475 | * collect an error message. | |||
1476 | * | |||
1477 | * IMPORTANT: If there is at least a single error, nothing will be loaded | |||
1478 | * and the rules will remain exactly as they were. | |||
1479 | * | |||
1480 | * At the end of the process, if no errors were found in the whole file then | |||
1481 | * NULL is returned. Otherwise an SDS string describing in a single line | |||
1482 | * a description of all the issues found is returned. */ | |||
1483 | sds ACLLoadFromFile(const char *filename) { | |||
1484 | FILE *fp; | |||
1485 | char buf[1024]; | |||
1486 | ||||
1487 | /* Open the ACL file. */ | |||
1488 | if ((fp = fopen(filename,"r")) == NULL((void*)0)) { | |||
1489 | sds errors = sdscatprintf(sdsempty(), | |||
1490 | "Error loading ACLs, opening file '%s': %s", | |||
1491 | filename, strerror(errno(*__errno_location ()))); | |||
1492 | return errors; | |||
1493 | } | |||
1494 | ||||
1495 | /* Load the whole file as a single string in memory. */ | |||
1496 | sds acls = sdsempty(); | |||
1497 | while(fgets(buf,sizeof(buf),fp) != NULL((void*)0)) | |||
1498 | acls = sdscat(acls,buf); | |||
1499 | fclose(fp); | |||
1500 | ||||
1501 | /* Split the file into lines and attempt to load each line. */ | |||
1502 | int totlines; | |||
1503 | sds *lines, errors = sdsempty(); | |||
1504 | lines = sdssplitlen(acls,strlen(acls),"\n",1,&totlines); | |||
1505 | sdsfree(acls); | |||
1506 | ||||
1507 | /* We need a fake user to validate the rules before making changes | |||
1508 | * to the real user mentioned in the ACL line. */ | |||
1509 | user *fakeuser = ACLCreateUnlinkedUser(); | |||
1510 | ||||
1511 | /* We do all the loading in a fresh instance of the Users radix tree, | |||
1512 | * so if there are errors loading the ACL file we can rollback to the | |||
1513 | * old version. */ | |||
1514 | rax *old_users = Users; | |||
1515 | user *old_default_user = DefaultUser; | |||
1516 | Users = raxNew(); | |||
1517 | ACLInitDefaultUser(); | |||
1518 | ||||
1519 | /* Load each line of the file. */ | |||
1520 | for (int i = 0; i < totlines; i++) { | |||
1521 | sds *argv; | |||
1522 | int argc; | |||
1523 | int linenum = i+1; | |||
1524 | ||||
1525 | lines[i] = sdstrim(lines[i]," \t\r\n"); | |||
1526 | ||||
1527 | /* Skip blank lines */ | |||
1528 | if (lines[i][0] == '\0') continue; | |||
1529 | ||||
1530 | /* Split into arguments */ | |||
1531 | argv = sdssplitlen(lines[i],sdslen(lines[i])," ",1,&argc); | |||
1532 | if (argv == NULL((void*)0)) { | |||
1533 | errors = sdscatprintf(errors, | |||
1534 | "%s:%d: unbalanced quotes in acl line. ", | |||
1535 | server.acl_filename, linenum); | |||
1536 | continue; | |||
1537 | } | |||
1538 | ||||
1539 | /* Skip this line if the resulting command vector is empty. */ | |||
1540 | if (argc == 0) { | |||
1541 | sdsfreesplitres(argv,argc); | |||
1542 | continue; | |||
1543 | } | |||
1544 | ||||
1545 | /* The line should start with the "user" keyword. */ | |||
1546 | if (strcmp(argv[0],"user") || argc < 2) { | |||
1547 | errors = sdscatprintf(errors, | |||
1548 | "%s:%d should start with user keyword followed " | |||
1549 | "by the username. ", server.acl_filename, | |||
1550 | linenum); | |||
1551 | sdsfreesplitres(argv,argc); | |||
1552 | continue; | |||
1553 | } | |||
1554 | ||||
1555 | /* Spaces are not allowed in usernames. */ | |||
1556 | if (ACLStringHasSpaces(argv[1],sdslen(argv[1]))) { | |||
1557 | errors = sdscatprintf(errors, | |||
1558 | "'%s:%d: username '%s' contains invalid characters. ", | |||
1559 | server.acl_filename, linenum, argv[1]); | |||
1560 | sdsfreesplitres(argv,argc); | |||
1561 | continue; | |||
1562 | } | |||
1563 | ||||
1564 | /* Try to process the line using the fake user to validate if | |||
1565 | * the rules are able to apply cleanly. At this stage we also | |||
1566 | * trim trailing spaces, so that we don't have to handle that | |||
1567 | * in ACLSetUser(). */ | |||
1568 | ACLSetUser(fakeuser,"reset",-1); | |||
1569 | int j; | |||
1570 | for (j = 2; j < argc; j++) { | |||
1571 | argv[j] = sdstrim(argv[j],"\t\r\n"); | |||
1572 | if (ACLSetUser(fakeuser,argv[j],sdslen(argv[j])) != C_OK0) { | |||
1573 | const char *errmsg = ACLSetUserStringError(); | |||
1574 | errors = sdscatprintf(errors, | |||
1575 | "%s:%d: %s. ", | |||
1576 | server.acl_filename, linenum, errmsg); | |||
1577 | continue; | |||
1578 | } | |||
1579 | } | |||
1580 | ||||
1581 | /* Apply the rule to the new users set only if so far there | |||
1582 | * are no errors, otherwise it's useless since we are going | |||
1583 | * to discard the new users set anyway. */ | |||
1584 | if (sdslen(errors) != 0) { | |||
1585 | sdsfreesplitres(argv,argc); | |||
1586 | continue; | |||
1587 | } | |||
1588 | ||||
1589 | /* We can finally lookup the user and apply the rule. If the | |||
1590 | * user already exists we always reset it to start. */ | |||
1591 | user *u = ACLCreateUser(argv[1],sdslen(argv[1])); | |||
1592 | if (!u) { | |||
1593 | u = ACLGetUserByName(argv[1],sdslen(argv[1])); | |||
1594 | serverAssert(u != NULL)((u != ((void*)0))?(void)0 : (_serverAssert("u != NULL","acl.c" ,1594),__builtin_unreachable())); | |||
1595 | ACLSetUser(u,"reset",-1); | |||
1596 | } | |||
1597 | ||||
1598 | /* Note that the same rules already applied to the fake user, so | |||
1599 | * we just assert that everything goes well: it should. */ | |||
1600 | for (j = 2; j < argc; j++) | |||
1601 | serverAssert(ACLSetUser(u,argv[j],sdslen(argv[j])) == C_OK)((ACLSetUser(u,argv[j],sdslen(argv[j])) == 0)?(void)0 : (_serverAssert ("ACLSetUser(u,argv[j],sdslen(argv[j])) == C_OK","acl.c",1601 ),__builtin_unreachable())); | |||
1602 | ||||
1603 | sdsfreesplitres(argv,argc); | |||
1604 | } | |||
1605 | ||||
1606 | ACLFreeUser(fakeuser); | |||
1607 | sdsfreesplitres(lines,totlines); | |||
1608 | DefaultUser = old_default_user; /* This pointer must never change. */ | |||
1609 | ||||
1610 | /* Check if we found errors and react accordingly. */ | |||
1611 | if (sdslen(errors) == 0) { | |||
1612 | /* The default user pointer is referenced in different places: instead | |||
1613 | * of replacing such occurrences it is much simpler to copy the new | |||
1614 | * default user configuration in the old one. */ | |||
1615 | user *new = ACLGetUserByName("default",7); | |||
1616 | serverAssert(new != NULL)((new != ((void*)0))?(void)0 : (_serverAssert("new != NULL","acl.c" ,1616),__builtin_unreachable())); | |||
1617 | ACLCopyUser(DefaultUser,new); | |||
1618 | ACLFreeUser(new); | |||
1619 | raxInsert(Users,(unsigned char*)"default",7,DefaultUser,NULL((void*)0)); | |||
1620 | raxRemove(old_users,(unsigned char*)"default",7,NULL((void*)0)); | |||
1621 | ACLFreeUsersSet(old_users); | |||
1622 | sdsfree(errors); | |||
1623 | return NULL((void*)0); | |||
1624 | } else { | |||
1625 | ACLFreeUsersSet(Users); | |||
1626 | Users = old_users; | |||
1627 | errors = sdscat(errors,"WARNING: ACL errors detected, no change to the previously active ACL rules was performed"); | |||
1628 | return errors; | |||
1629 | } | |||
1630 | } | |||
1631 | ||||
1632 | /* Generate a copy of the ACLs currently in memory in the specified filename. | |||
1633 | * Returns C_OK on success or C_ERR if there was an error during the I/O. | |||
1634 | * When C_ERR is returned a log is produced with hints about the issue. */ | |||
1635 | int ACLSaveToFile(const char *filename) { | |||
1636 | sds acl = sdsempty(); | |||
1637 | int fd = -1; | |||
1638 | sds tmpfilename = NULL((void*)0); | |||
1639 | int retval = C_ERR-1; | |||
1640 | ||||
1641 | /* Let's generate an SDS string containing the new version of the | |||
1642 | * ACL file. */ | |||
1643 | raxIterator ri; | |||
1644 | raxStart(&ri,Users); | |||
1645 | raxSeek(&ri,"^",NULL((void*)0),0); | |||
1646 | while(raxNext(&ri)) { | |||
1647 | user *u = ri.data; | |||
1648 | /* Return information in the configuration file format. */ | |||
1649 | sds user = sdsnew("user "); | |||
1650 | user = sdscatsds(user,u->name); | |||
1651 | user = sdscatlen(user," ",1); | |||
1652 | sds descr = ACLDescribeUser(u); | |||
1653 | user = sdscatsds(user,descr); | |||
1654 | sdsfree(descr); | |||
1655 | acl = sdscatsds(acl,user); | |||
1656 | acl = sdscatlen(acl,"\n",1); | |||
1657 | sdsfree(user); | |||
1658 | } | |||
1659 | raxStop(&ri); | |||
1660 | ||||
1661 | /* Create a temp file with the new content. */ | |||
1662 | tmpfilename = sdsnew(filename); | |||
1663 | tmpfilename = sdscatfmt(tmpfilename,".tmp-%i-%I", | |||
1664 | (int)getpid(),(int)mstime()); | |||
1665 | if ((fd = open(tmpfilename,O_WRONLY01|O_CREAT0100,0644)) == -1) { | |||
1666 | serverLog(LL_WARNING3,"Opening temp ACL file for ACL SAVE: %s", | |||
1667 | strerror(errno(*__errno_location ()))); | |||
1668 | goto cleanup; | |||
1669 | } | |||
1670 | ||||
1671 | /* Write it. */ | |||
1672 | if (write(fd,acl,sdslen(acl)) != (ssize_t)sdslen(acl)) { | |||
1673 | serverLog(LL_WARNING3,"Writing ACL file for ACL SAVE: %s", | |||
1674 | strerror(errno(*__errno_location ()))); | |||
1675 | goto cleanup; | |||
1676 | } | |||
1677 | close(fd); fd = -1; | |||
1678 | ||||
1679 | /* Let's replace the new file with the old one. */ | |||
1680 | if (rename(tmpfilename,filename) == -1) { | |||
1681 | serverLog(LL_WARNING3,"Renaming ACL file for ACL SAVE: %s", | |||
1682 | strerror(errno(*__errno_location ()))); | |||
1683 | goto cleanup; | |||
1684 | } | |||
1685 | sdsfree(tmpfilename); tmpfilename = NULL((void*)0); | |||
1686 | retval = C_OK0; /* If we reached this point, everything is fine. */ | |||
1687 | ||||
1688 | cleanup: | |||
1689 | if (fd != -1) close(fd); | |||
1690 | if (tmpfilename) unlink(tmpfilename); | |||
1691 | sdsfree(tmpfilename); | |||
1692 | sdsfree(acl); | |||
1693 | return retval; | |||
1694 | } | |||
1695 | ||||
1696 | /* This function is called once the server is already running, modules are | |||
1697 | * loaded, and we are ready to start, in order to load the ACLs either from | |||
1698 | * the pending list of users defined in redis.conf, or from the ACL file. | |||
1699 | * The function will just exit with an error if the user is trying to mix | |||
1700 | * both the loading methods. */ | |||
1701 | void ACLLoadUsersAtStartup(void) { | |||
1702 | if (server.acl_filename[0] != '\0' && listLength(UsersToLoad)((UsersToLoad)->len) != 0) { | |||
1703 | serverLog(LL_WARNING3, | |||
1704 | "Configuring Redis with users defined in redis.conf and at " | |||
1705 | "the same setting an ACL file path is invalid. This setup " | |||
1706 | "is very likely to lead to configuration errors and security " | |||
1707 | "holes, please define either an ACL file or declare users " | |||
1708 | "directly in your redis.conf, but not both."); | |||
1709 | exit(1); | |||
1710 | } | |||
1711 | ||||
1712 | if (ACLLoadConfiguredUsers() == C_ERR-1) { | |||
1713 | serverLog(LL_WARNING3, | |||
1714 | "Critical error while loading ACLs. Exiting."); | |||
1715 | exit(1); | |||
1716 | } | |||
1717 | ||||
1718 | if (server.acl_filename[0] != '\0') { | |||
1719 | sds errors = ACLLoadFromFile(server.acl_filename); | |||
1720 | if (errors) { | |||
1721 | serverLog(LL_WARNING3, | |||
1722 | "Aborting Redis startup because of ACL errors: %s", errors); | |||
1723 | sdsfree(errors); | |||
1724 | exit(1); | |||
1725 | } | |||
1726 | } | |||
1727 | } | |||
1728 | ||||
1729 | /* ============================================================================= | |||
1730 | * ACL log | |||
1731 | * ==========================================================================*/ | |||
1732 | ||||
1733 | #define ACL_LOG_CTX_TOPLEVEL0 0 | |||
1734 | #define ACL_LOG_CTX_LUA1 1 | |||
1735 | #define ACL_LOG_CTX_MULTI2 2 | |||
1736 | #define ACL_LOG_GROUPING_MAX_TIME_DELTA60000 60000 | |||
1737 | ||||
1738 | /* This structure defines an entry inside the ACL log. */ | |||
1739 | typedef struct ACLLogEntry { | |||
1740 | uint64_t count; /* Number of times this happened recently. */ | |||
1741 | int reason; /* Reason for denying the command. ACL_DENIED_*. */ | |||
1742 | int context; /* Toplevel, Lua or MULTI/EXEC? ACL_LOG_CTX_*. */ | |||
1743 | sds object; /* The key name or command name. */ | |||
1744 | sds username; /* User the client is authenticated with. */ | |||
1745 | mstime_t ctime; /* Milliseconds time of last update to this entry. */ | |||
1746 | sds cinfo; /* Client info (last client if updated). */ | |||
1747 | } ACLLogEntry; | |||
1748 | ||||
1749 | /* This function will check if ACL entries 'a' and 'b' are similar enough | |||
1750 | * that we should actually update the existing entry in our ACL log instead | |||
1751 | * of creating a new one. */ | |||
1752 | int ACLLogMatchEntry(ACLLogEntry *a, ACLLogEntry *b) { | |||
1753 | if (a->reason != b->reason) return 0; | |||
1754 | if (a->context != b->context) return 0; | |||
1755 | mstime_t delta = a->ctime - b->ctime; | |||
1756 | if (delta < 0) delta = -delta; | |||
1757 | if (delta > ACL_LOG_GROUPING_MAX_TIME_DELTA60000) return 0; | |||
1758 | if (sdscmp(a->object,b->object) != 0) return 0; | |||
1759 | if (sdscmp(a->username,b->username) != 0) return 0; | |||
1760 | return 1; | |||
1761 | } | |||
1762 | ||||
1763 | /* Release an ACL log entry. */ | |||
1764 | void ACLFreeLogEntry(void *leptr) { | |||
1765 | ACLLogEntry *le = leptr; | |||
1766 | sdsfree(le->object); | |||
1767 | sdsfree(le->username); | |||
1768 | sdsfree(le->cinfo); | |||
1769 | zfree(le); | |||
1770 | } | |||
1771 | ||||
1772 | /* Adds a new entry in the ACL log, making sure to delete the old entry | |||
1773 | * if we reach the maximum length allowed for the log. This function attempts | |||
1774 | * to find similar entries in the current log in order to bump the counter of | |||
1775 | * the log entry instead of creating many entries for very similar ACL | |||
1776 | * rules issues. | |||
1777 | * | |||
1778 | * The argpos argument is used when the reason is ACL_DENIED_KEY or | |||
1779 | * ACL_DENIED_CHANNEL, since it allows the function to log the key or channel | |||
1780 | * name that caused the problem. Similarly the username is only passed when we | |||
1781 | * failed to authenticate the user with AUTH or HELLO, for the ACL_DENIED_AUTH | |||
1782 | * reason. Otherwise it will just be NULL. | |||
1783 | */ | |||
1784 | void addACLLogEntry(client *c, int reason, int argpos, sds username) { | |||
1785 | /* Create a new entry. */ | |||
1786 | struct ACLLogEntry *le = zmalloc(sizeof(*le)); | |||
1787 | le->count = 1; | |||
1788 | le->reason = reason; | |||
1789 | le->username = sdsdup(reason == ACL_DENIED_AUTH3 ? username : c->user->name); | |||
1790 | le->ctime = mstime(); | |||
1791 | ||||
1792 | switch(reason) { | |||
1793 | case ACL_DENIED_CMD1: le->object = sdsnew(c->cmd->name); break; | |||
1794 | case ACL_DENIED_KEY2: le->object = sdsdup(c->argv[argpos]->ptr); break; | |||
1795 | case ACL_DENIED_CHANNEL4: le->object = sdsdup(c->argv[argpos]->ptr); break; | |||
1796 | case ACL_DENIED_AUTH3: le->object = sdsdup(c->argv[0]->ptr); break; | |||
1797 | default: le->object = sdsempty(); | |||
1798 | } | |||
1799 | ||||
1800 | client *realclient = c; | |||
1801 | if (realclient->flags & CLIENT_LUA(1<<8)) realclient = server.lua_caller; | |||
1802 | ||||
1803 | le->cinfo = catClientInfoString(sdsempty(),realclient); | |||
1804 | if (c->flags & CLIENT_MULTI(1<<3)) { | |||
1805 | le->context = ACL_LOG_CTX_MULTI2; | |||
1806 | } else if (c->flags & CLIENT_LUA(1<<8)) { | |||
1807 | le->context = ACL_LOG_CTX_LUA1; | |||
1808 | } else { | |||
1809 | le->context = ACL_LOG_CTX_TOPLEVEL0; | |||
1810 | } | |||
1811 | ||||
1812 | /* Try to match this entry with past ones, to see if we can just | |||
1813 | * update an existing entry instead of creating a new one. */ | |||
1814 | long toscan = 10; /* Do a limited work trying to find duplicated. */ | |||
1815 | listIter li; | |||
1816 | listNode *ln; | |||
1817 | listRewind(ACLLog,&li); | |||
1818 | ACLLogEntry *match = NULL((void*)0); | |||
1819 | while (toscan-- && (ln = listNext(&li)) != NULL((void*)0)) { | |||
1820 | ACLLogEntry *current = listNodeValue(ln)((ln)->value); | |||
1821 | if (ACLLogMatchEntry(current,le)) { | |||
1822 | match = current; | |||
1823 | listDelNode(ACLLog,ln); | |||
1824 | listAddNodeHead(ACLLog,current); | |||
1825 | break; | |||
1826 | } | |||
1827 | } | |||
1828 | ||||
1829 | /* If there is a match update the entry, otherwise add it as a | |||
1830 | * new one. */ | |||
1831 | if (match) { | |||
1832 | /* We update a few fields of the existing entry and bump the | |||
1833 | * counter of events for this entry. */ | |||
1834 | sdsfree(match->cinfo); | |||
1835 | match->cinfo = le->cinfo; | |||
1836 | match->ctime = le->ctime; | |||
1837 | match->count++; | |||
1838 | ||||
1839 | /* Release the old entry. */ | |||
1840 | le->cinfo = NULL((void*)0); | |||
1841 | ACLFreeLogEntry(le); | |||
1842 | } else { | |||
1843 | /* Add it to our list of entries. We'll have to trim the list | |||
1844 | * to its maximum size. */ | |||
1845 | listAddNodeHead(ACLLog, le); | |||
1846 | while(listLength(ACLLog)((ACLLog)->len) > server.acllog_max_len) { | |||
1847 | listNode *ln = listLast(ACLLog)((ACLLog)->tail); | |||
1848 | ACLLogEntry *le = listNodeValue(ln)((ln)->value); | |||
1849 | ACLFreeLogEntry(le); | |||
1850 | listDelNode(ACLLog,ln); | |||
1851 | } | |||
1852 | } | |||
1853 | } | |||
1854 | ||||
1855 | /* ============================================================================= | |||
1856 | * ACL related commands | |||
1857 | * ==========================================================================*/ | |||
1858 | ||||
1859 | /* ACL -- show and modify the configuration of ACL users. | |||
1860 | * ACL HELP | |||
1861 | * ACL LOAD | |||
1862 | * ACL SAVE | |||
1863 | * ACL LIST | |||
1864 | * ACL USERS | |||
1865 | * ACL CAT [<category>] | |||
1866 | * ACL SETUSER <username> ... acl rules ... | |||
1867 | * ACL DELUSER <username> [...] | |||
1868 | * ACL GETUSER <username> | |||
1869 | * ACL GENPASS [<bits>] | |||
1870 | * ACL WHOAMI | |||
1871 | * ACL LOG [<count> | RESET] | |||
1872 | */ | |||
1873 | void aclCommand(client *c) { | |||
1874 | char *sub = c->argv[1]->ptr; | |||
1875 | if (!strcasecmp(sub,"setuser") && c->argc >= 3) { | |||
| ||||
1876 | sds username = c->argv[2]->ptr; | |||
1877 | /* Check username validity. */ | |||
1878 | if (ACLStringHasSpaces(username,sdslen(username))) { | |||
1879 | addReplyErrorFormat(c, | |||
1880 | "Usernames can't contain spaces or null characters"); | |||
1881 | return; | |||
1882 | } | |||
1883 | ||||
1884 | /* Create a temporary user to validate and stage all changes against | |||
1885 | * before applying to an existing user or creating a new user. If all | |||
1886 | * arguments are valid the user parameters will all be applied together. | |||
1887 | * If there are any errors then none of the changes will be applied. */ | |||
1888 | user *tempu = ACLCreateUnlinkedUser(); | |||
1889 | user *u = ACLGetUserByName(username,sdslen(username)); | |||
1890 | if (u) ACLCopyUser(tempu, u); | |||
1891 | ||||
1892 | for (int j = 3; j < c->argc; j++) { | |||
1893 | if (ACLSetUser(tempu,c->argv[j]->ptr,sdslen(c->argv[j]->ptr)) != C_OK0) { | |||
1894 | const char *errmsg = ACLSetUserStringError(); | |||
1895 | addReplyErrorFormat(c, | |||
1896 | "Error in ACL SETUSER modifier '%s': %s", | |||
1897 | (char*)c->argv[j]->ptr, errmsg); | |||
1898 | ||||
1899 | ACLFreeUser(tempu); | |||
1900 | return; | |||
1901 | } | |||
1902 | } | |||
1903 | ||||
1904 | /* Existing pub/sub clients authenticated with the user may need to be | |||
1905 | * disconnected if (some of) their channel permissions were revoked. */ | |||
1906 | if (u && !(tempu->flags & USER_FLAG_ALLCHANNELS(1<<5))) | |||
1907 | ACLKillPubsubClientsIfNeeded(u,tempu->channels); | |||
1908 | ||||
1909 | /* Overwrite the user with the temporary user we modified above. */ | |||
1910 | if (!u) u = ACLCreateUser(username,sdslen(username)); | |||
1911 | serverAssert(u != NULL)((u != ((void*)0))?(void)0 : (_serverAssert("u != NULL","acl.c" ,1911),__builtin_unreachable())); | |||
1912 | ACLCopyUser(u, tempu); | |||
1913 | ACLFreeUser(tempu); | |||
1914 | addReply(c,shared.ok); | |||
1915 | } else if (!strcasecmp(sub,"deluser") && c->argc >= 3) { | |||
1916 | int deleted = 0; | |||
1917 | for (int j = 2; j < c->argc; j++) { | |||
1918 | sds username = c->argv[j]->ptr; | |||
1919 | if (!strcmp(username,"default")) { | |||
1920 | addReplyError(c,"The 'default' user cannot be removed"); | |||
1921 | return; | |||
1922 | } | |||
1923 | } | |||
1924 | ||||
1925 | for (int j = 2; j < c->argc; j++) { | |||
1926 | sds username = c->argv[j]->ptr; | |||
1927 | user *u; | |||
1928 | if (raxRemove(Users,(unsigned char*)username, | |||
1929 | sdslen(username), | |||
1930 | (void**)&u)) | |||
1931 | { | |||
1932 | ACLFreeUserAndKillClients(u); | |||
1933 | deleted++; | |||
1934 | } | |||
1935 | } | |||
1936 | addReplyLongLong(c,deleted); | |||
1937 | } else if (!strcasecmp(sub,"getuser") && c->argc == 3) { | |||
1938 | user *u = ACLGetUserByName(c->argv[2]->ptr,sdslen(c->argv[2]->ptr)); | |||
1939 | if (u == NULL((void*)0)) { | |||
1940 | addReplyNull(c); | |||
1941 | return; | |||
1942 | } | |||
1943 | ||||
1944 | addReplyMapLen(c,5); | |||
1945 | ||||
1946 | /* Flags */ | |||
1947 | addReplyBulkCString(c,"flags"); | |||
1948 | void *deflen = addReplyDeferredLen(c); | |||
1949 | int numflags = 0; | |||
1950 | for (int j = 0; ACLUserFlags[j].flag; j++) { | |||
1951 | if (u->flags & ACLUserFlags[j].flag) { | |||
1952 | addReplyBulkCString(c,ACLUserFlags[j].name); | |||
1953 | numflags++; | |||
1954 | } | |||
1955 | } | |||
1956 | setDeferredSetLen(c,deflen,numflags); | |||
1957 | ||||
1958 | /* Passwords */ | |||
1959 | addReplyBulkCString(c,"passwords"); | |||
1960 | addReplyArrayLen(c,listLength(u->passwords)((u->passwords)->len)); | |||
1961 | listIter li; | |||
1962 | listNode *ln; | |||
1963 | listRewind(u->passwords,&li); | |||
1964 | while((ln = listNext(&li))) { | |||
1965 | sds thispass = listNodeValue(ln)((ln)->value); | |||
1966 | addReplyBulkCBuffer(c,thispass,sdslen(thispass)); | |||
1967 | } | |||
1968 | ||||
1969 | /* Commands */ | |||
1970 | addReplyBulkCString(c,"commands"); | |||
1971 | sds cmddescr = ACLDescribeUserCommandRules(u); | |||
1972 | addReplyBulkSds(c,cmddescr); | |||
1973 | ||||
1974 | /* Key patterns */ | |||
1975 | addReplyBulkCString(c,"keys"); | |||
1976 | if (u->flags & USER_FLAG_ALLKEYS(1<<2)) { | |||
1977 | addReplyArrayLen(c,1); | |||
1978 | addReplyBulkCBuffer(c,"*",1); | |||
1979 | } else { | |||
1980 | addReplyArrayLen(c,listLength(u->patterns)((u->patterns)->len)); | |||
1981 | listIter li; | |||
1982 | listNode *ln; | |||
1983 | listRewind(u->patterns,&li); | |||
1984 | while((ln = listNext(&li))) { | |||
1985 | sds thispat = listNodeValue(ln)((ln)->value); | |||
1986 | addReplyBulkCBuffer(c,thispat,sdslen(thispat)); | |||
1987 | } | |||
1988 | } | |||
1989 | ||||
1990 | /* Pub/sub patterns */ | |||
1991 | addReplyBulkCString(c,"channels"); | |||
1992 | if (u->flags & USER_FLAG_ALLCHANNELS(1<<5)) { | |||
1993 | addReplyArrayLen(c,1); | |||
1994 | addReplyBulkCBuffer(c,"*",1); | |||
1995 | } else { | |||
1996 | addReplyArrayLen(c,listLength(u->channels)((u->channels)->len)); | |||
1997 | listIter li; | |||
1998 | listNode *ln; | |||
1999 | listRewind(u->channels,&li); | |||
2000 | while((ln = listNext(&li))) { | |||
2001 | sds thispat = listNodeValue(ln)((ln)->value); | |||
2002 | addReplyBulkCBuffer(c,thispat,sdslen(thispat)); | |||
2003 | } | |||
2004 | } | |||
2005 | } else if ((!strcasecmp(sub,"list") || !strcasecmp(sub,"users")) && | |||
2006 | c->argc == 2) | |||
2007 | { | |||
2008 | int justnames = !strcasecmp(sub,"users"); | |||
2009 | addReplyArrayLen(c,raxSize(Users)); | |||
2010 | raxIterator ri; | |||
2011 | raxStart(&ri,Users); | |||
2012 | raxSeek(&ri,"^",NULL((void*)0),0); | |||
2013 | while(raxNext(&ri)) { | |||
2014 | user *u = ri.data; | |||
2015 | if (justnames) { | |||
2016 | addReplyBulkCBuffer(c,u->name,sdslen(u->name)); | |||
2017 | } else { | |||
2018 | /* Return information in the configuration file format. */ | |||
2019 | sds config = sdsnew("user "); | |||
2020 | config = sdscatsds(config,u->name); | |||
2021 | config = sdscatlen(config," ",1); | |||
2022 | sds descr = ACLDescribeUser(u); | |||
2023 | config = sdscatsds(config,descr); | |||
2024 | sdsfree(descr); | |||
2025 | addReplyBulkSds(c,config); | |||
2026 | } | |||
2027 | } | |||
2028 | raxStop(&ri); | |||
2029 | } else if (!strcasecmp(sub,"whoami") && c->argc == 2) { | |||
2030 | if (c->user != NULL((void*)0)) { | |||
2031 | addReplyBulkCBuffer(c,c->user->name,sdslen(c->user->name)); | |||
2032 | } else { | |||
2033 | addReplyNull(c); | |||
2034 | } | |||
2035 | } else if (server.acl_filename[0] == '\0' && | |||
2036 | (!strcasecmp(sub,"load") || !strcasecmp(sub,"save"))) | |||
2037 | { | |||
2038 | 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."); | |||
2039 | return; | |||
2040 | } else if (!strcasecmp(sub,"load") && c->argc == 2) { | |||
2041 | sds errors = ACLLoadFromFile(server.acl_filename); | |||
2042 | if (errors == NULL((void*)0)) { | |||
2043 | addReply(c,shared.ok); | |||
2044 | } else { | |||
2045 | addReplyError(c,errors); | |||
2046 | sdsfree(errors); | |||
2047 | } | |||
2048 | } else if (!strcasecmp(sub,"save") && c->argc == 2) { | |||
2049 | if (ACLSaveToFile(server.acl_filename) == C_OK0) { | |||
2050 | addReply(c,shared.ok); | |||
2051 | } else { | |||
2052 | addReplyError(c,"There was an error trying to save the ACLs. " | |||
2053 | "Please check the server logs for more " | |||
2054 | "information"); | |||
2055 | } | |||
2056 | } else if (!strcasecmp(sub,"cat") && c->argc == 2) { | |||
2057 | void *dl = addReplyDeferredLen(c); | |||
2058 | int j; | |||
2059 | for (j = 0; ACLCommandCategories[j].flag != 0; j++) | |||
2060 | addReplyBulkCString(c,ACLCommandCategories[j].name); | |||
2061 | setDeferredArrayLen(c,dl,j); | |||
2062 | } else if (!strcasecmp(sub,"cat") && c->argc == 3) { | |||
2063 | uint64_t cflag = ACLGetCommandCategoryFlagByName(c->argv[2]->ptr); | |||
2064 | if (cflag == 0) { | |||
2065 | addReplyErrorFormat(c, "Unknown category '%s'", (char*)c->argv[2]->ptr); | |||
2066 | return; | |||
2067 | } | |||
2068 | int arraylen = 0; | |||
2069 | void *dl = addReplyDeferredLen(c); | |||
2070 | dictIterator *di = dictGetIterator(server.orig_commands); | |||
2071 | dictEntry *de; | |||
2072 | while ((de = dictNext(di)) != NULL((void*)0)) { | |||
2073 | struct redisCommand *cmd = dictGetVal(de)((de)->v.val); | |||
2074 | if (cmd->flags & CMD_MODULE(1ULL<<3)) continue; | |||
2075 | if (cmd->flags & cflag) { | |||
2076 | addReplyBulkCString(c,cmd->name); | |||
2077 | arraylen++; | |||
2078 | } | |||
2079 | } | |||
2080 | dictReleaseIterator(di); | |||
2081 | setDeferredArrayLen(c,dl,arraylen); | |||
2082 | } else if (!strcasecmp(sub,"genpass") && (c->argc == 2 || c->argc == 3)) { | |||
2083 | #define GENPASS_MAX_BITS4096 4096 | |||
2084 | char pass[GENPASS_MAX_BITS4096/8*2]; /* Hex representation. */ | |||
2085 | long bits = 256; /* By default generate 256 bits passwords. */ | |||
2086 | ||||
2087 | if (c->argc == 3 && getLongFromObjectOrReply(c,c->argv[2],&bits,NULL((void*)0)) | |||
2088 | != C_OK0) return; | |||
2089 | ||||
2090 | if (bits <= 0 || bits > GENPASS_MAX_BITS4096) { | |||
2091 | addReplyErrorFormat(c, | |||
2092 | "ACL GENPASS argument must be the number of " | |||
2093 | "bits for the output password, a positive number " | |||
2094 | "up to %d",GENPASS_MAX_BITS4096); | |||
2095 | return; | |||
2096 | } | |||
2097 | ||||
2098 | long chars = (bits+3)/4; /* Round to number of characters to emit. */ | |||
2099 | getRandomHexChars(pass,chars); | |||
2100 | addReplyBulkCBuffer(c,pass,chars); | |||
2101 | } else if (!strcasecmp(sub,"log") && (c->argc == 2 || c->argc ==3)) { | |||
2102 | long count = 10; /* Number of entries to emit by default. */ | |||
2103 | ||||
2104 | /* Parse the only argument that LOG may have: it could be either | |||
2105 | * the number of entries the user wants to display, or alternatively | |||
2106 | * the "RESET" command in order to flush the old entries. */ | |||
2107 | if (c->argc == 3) { | |||
2108 | if (!strcasecmp(c->argv[2]->ptr,"reset")) { | |||
2109 | listSetFreeMethod(ACLLog,ACLFreeLogEntry)((ACLLog)->free = (ACLFreeLogEntry)); | |||
2110 | listEmpty(ACLLog); | |||
2111 | listSetFreeMethod(ACLLog,NULL)((ACLLog)->free = (((void*)0))); | |||
2112 | addReply(c,shared.ok); | |||
2113 | return; | |||
2114 | } else if (getLongFromObjectOrReply(c,c->argv[2],&count,NULL((void*)0)) | |||
2115 | != C_OK0) | |||
2116 | { | |||
2117 | return; | |||
2118 | } | |||
2119 | if (count < 0) count = 0; | |||
2120 | } | |||
2121 | ||||
2122 | /* Fix the count according to the number of entries we got. */ | |||
2123 | if ((size_t)count > listLength(ACLLog)((ACLLog)->len)) | |||
2124 | count = listLength(ACLLog)((ACLLog)->len); | |||
2125 | ||||
2126 | addReplyArrayLen(c,count); | |||
2127 | listIter li; | |||
2128 | listNode *ln; | |||
2129 | listRewind(ACLLog,&li); | |||
2130 | mstime_t now = mstime(); | |||
2131 | while (count-- && (ln = listNext(&li)) != NULL((void*)0)) { | |||
2132 | ACLLogEntry *le = listNodeValue(ln)((ln)->value); | |||
2133 | addReplyMapLen(c,7); | |||
2134 | addReplyBulkCString(c,"count"); | |||
2135 | addReplyLongLong(c,le->count); | |||
2136 | ||||
2137 | addReplyBulkCString(c,"reason"); | |||
2138 | char *reasonstr; | |||
2139 | switch(le->reason) { | |||
2140 | case ACL_DENIED_CMD1: reasonstr="command"; break; | |||
2141 | case ACL_DENIED_KEY2: reasonstr="key"; break; | |||
2142 | case ACL_DENIED_CHANNEL4: reasonstr="channel"; break; | |||
2143 | case ACL_DENIED_AUTH3: reasonstr="auth"; break; | |||
2144 | default: reasonstr="unknown"; | |||
2145 | } | |||
2146 | addReplyBulkCString(c,reasonstr); | |||
2147 | ||||
2148 | addReplyBulkCString(c,"context"); | |||
2149 | char *ctxstr; | |||
2150 | switch(le->context) { | |||
2151 | case ACL_LOG_CTX_TOPLEVEL0: ctxstr="toplevel"; break; | |||
2152 | case ACL_LOG_CTX_MULTI2: ctxstr="multi"; break; | |||
2153 | case ACL_LOG_CTX_LUA1: ctxstr="lua"; break; | |||
2154 | default: ctxstr="unknown"; | |||
2155 | } | |||
2156 | addReplyBulkCString(c,ctxstr); | |||
2157 | ||||
2158 | addReplyBulkCString(c,"object"); | |||
2159 | addReplyBulkCBuffer(c,le->object,sdslen(le->object)); | |||
2160 | addReplyBulkCString(c,"username"); | |||
2161 | addReplyBulkCBuffer(c,le->username,sdslen(le->username)); | |||
2162 | addReplyBulkCString(c,"age-seconds"); | |||
2163 | double age = (double)(now - le->ctime)/1000; | |||
2164 | addReplyDouble(c,age); | |||
2165 | addReplyBulkCString(c,"client-info"); | |||
2166 | addReplyBulkCBuffer(c,le->cinfo,sdslen(le->cinfo)); | |||
2167 | } | |||
2168 | } else if (c->argc == 2 && !strcasecmp(sub,"help")) { | |||
2169 | const char *help[] = { | |||
2170 | "CAT [<category>]", | |||
2171 | " List all commands that belong to <category>, or all command categories", | |||
2172 | " when no category is specified.", | |||
2173 | "DELUSER <username> [<username> ...]", | |||
2174 | " Delete a list of users.", | |||
2175 | "GETUSER <username>", | |||
2176 | " Get the user's details.", | |||
2177 | "GENPASS [<bits>]", | |||
2178 | " Generate a secure 256-bit user password. The optional `bits` argument can", | |||
2179 | " be used to specify a different size.", | |||
2180 | "LIST", | |||
2181 | " Show users details in config file format.", | |||
2182 | "LOAD", | |||
2183 | " Reload users from the ACL file.", | |||
2184 | "LOG [<count> | RESET]", | |||
2185 | " Show the ACL log entries.", | |||
2186 | "SAVE", | |||
2187 | " Save the current config to the ACL file.", | |||
2188 | "SETUSER <username> <attribute> [<attribute> ...]", | |||
2189 | " Create or modify a user with the specified attributes.", | |||
2190 | "USERS", | |||
2191 | " List all the registered usernames.", | |||
2192 | "WHOAMI", | |||
2193 | " Return the current connection username.", | |||
2194 | NULL((void*)0) | |||
2195 | }; | |||
2196 | addReplyHelp(c,help); | |||
2197 | } else { | |||
2198 | addReplySubcommandSyntaxError(c); | |||
2199 | } | |||
2200 | } | |||
2201 | ||||
2202 | void addReplyCommandCategories(client *c, struct redisCommand *cmd) { | |||
2203 | int flagcount = 0; | |||
2204 | void *flaglen = addReplyDeferredLen(c); | |||
2205 | for (int j = 0; ACLCommandCategories[j].flag != 0; j++) { | |||
2206 | if (cmd->flags & ACLCommandCategories[j].flag) { | |||
2207 | addReplyStatusFormat(c, "@%s", ACLCommandCategories[j].name); | |||
2208 | flagcount++; | |||
2209 | } | |||
2210 | } | |||
2211 | setDeferredSetLen(c, flaglen, flagcount); | |||
2212 | } | |||
2213 | ||||
2214 | /* AUTH <password> | |||
2215 | * AUTH <username> <password> (Redis >= 6.0 form) | |||
2216 | * | |||
2217 | * When the user is omitted it means that we are trying to authenticate | |||
2218 | * against the default user. */ | |||
2219 | void authCommand(client *c) { | |||
2220 | /* Only two or three argument forms are allowed. */ | |||
2221 | if (c->argc > 3) { | |||
2222 | addReplyErrorObject(c,shared.syntaxerr); | |||
2223 | return; | |||
2224 | } | |||
2225 | ||||
2226 | /* Handle the two different forms here. The form with two arguments | |||
2227 | * will just use "default" as username. */ | |||
2228 | robj *username, *password; | |||
2229 | if (c->argc == 2) { | |||
2230 | /* Mimic the old behavior of giving an error for the two commands | |||
2231 | * from if no password is configured. */ | |||
2232 | if (DefaultUser->flags & USER_FLAG_NOPASS(1<<4)) { | |||
2233 | addReplyError(c,"AUTH <password> called without any password " | |||
2234 | "configured for the default user. Are you sure " | |||
2235 | "your configuration is correct?"); | |||
2236 | return; | |||
2237 | } | |||
2238 | ||||
2239 | username = shared.default_username; | |||
2240 | password = c->argv[1]; | |||
2241 | } else { | |||
2242 | username = c->argv[1]; | |||
2243 | password = c->argv[2]; | |||
2244 | } | |||
2245 | ||||
2246 | if (ACLAuthenticateUser(c,username,password) == C_OK0) { | |||
2247 | addReply(c,shared.ok); | |||
2248 | } else { | |||
2249 | addReplyError(c,"-WRONGPASS invalid username-password pair or user is disabled."); | |||
2250 | } | |||
2251 | } | |||
2252 | ||||
2253 | /* Set the password for the "default" ACL user. This implements supports for | |||
2254 | * requirepass config, so passing in NULL will set the user to be nopass. */ | |||
2255 | void ACLUpdateDefaultUserPassword(sds password) { | |||
2256 | ACLSetUser(DefaultUser,"resetpass",-1); | |||
2257 | if (password) { | |||
2258 | sds aclop = sdscatlen(sdsnew(">"), password, sdslen(password)); | |||
2259 | ACLSetUser(DefaultUser,aclop,sdslen(aclop)); | |||
2260 | sdsfree(aclop); | |||
2261 | } else { | |||
2262 | ACLSetUser(DefaultUser,"nopass",-1); | |||
2263 | } | |||
2264 | } |