From abcb223ec05740c3534b31e3c756ac0d63f8c07e Mon Sep 17 00:00:00 2001 From: Brian Hammond Date: Wed, 25 Mar 2009 23:03:56 -0400 Subject: [PATCH] Adds AUTH command. $ make cc -c -O2 -Wall -W -DSDS_ABORT_ON_OOM -g adlist.c cc -c -O2 -Wall -W -DSDS_ABORT_ON_OOM -g ae.c cc -c -O2 -Wall -W -DSDS_ABORT_ON_OOM -g anet.c cc -c -O2 -Wall -W -DSDS_ABORT_ON_OOM -g dict.c cc -c -O2 -Wall -W -DSDS_ABORT_ON_OOM -g redis.c cc -c -O2 -Wall -W -DSDS_ABORT_ON_OOM -g sds.c cc -c -O2 -Wall -W -DSDS_ABORT_ON_OOM -g zmalloc.c cc -o redis-server -O2 -Wall -W -DSDS_ABORT_ON_OOM -g adlist.o ae.o anet.o dict.o redis.o sds.o zmalloc.o Hint: To run the test-redis.tcl script is a good idea. Launch the redis server with ./redis-server, then in another terminal window enter this directory and run 'make test'. cc -c -O2 -Wall -W -DSDS_ABORT_ON_OOM -g benchmark.c cc -o redis-benchmark -O2 -Wall -W -DSDS_ABORT_ON_OOM -g ae.o anet.o benchmark.o sds.o adlist.o zmalloc.o cc -c -O2 -Wall -W -DSDS_ABORT_ON_OOM -g redis-cli.c cc -o redis-cli -O2 -Wall -W -DSDS_ABORT_ON_OOM -g anet.o sds.o adlist.o redis-cli.o zmalloc.o $ tail -1 redis.conf $ ./redis-server redis.conf & [1] 52494 $ - Server started, Redis version 0.08 - DB loaded from disk - The server is now ready to accept connections on port 6379 . 0 clients connected (0 slaves), 1932 bytes in use $ make test tclsh test-redis.tcl DEL all keys to start with a clean DB . Accepted 127.0.0.1:54490 PASSED SET and GET an item PASSED DEL against a single item PASSED KEYS with pattern PASSED KEYS to get all keys PASSED DBSIZE PASSED DEL all keys PASSED Very big payload in GET/SET PASSED SET 10000 numeric keys and access all them in reverse order . DB 0: 10001 keys in 16384 slots HT. . 1 clients connected (0 slaves), 4885707 bytes in use PASSED DBSIZE should be 10001 now PASSED INCR against non existing key PASSED INCR against key created by incr itself PASSED INCR against key originally set with SET PASSED SETNX target key missing PASSED SETNX target key exists PASSED EXISTS PASSED Zero length value in key. SET/GET/EXISTS PASSED Commands pipelining PASSED Non existing command PASSED Basic LPUSH, RPUSH, LLENGTH, LINDEX PASSED DEL a list PASSED Create a long list and check every single element with LINDEX PASSED Test elements with LINDEX in random access PASSED LLEN against non-list value error PASSED LINDEX against non-list value error PASSED LPUSH against non-list value error PASSED RPUSH against non-list value error PASSED RENAME basic usage PASSED RENAME source key should no longer exist PASSED RENAME against already existing key PASSED RENAMENX basic usage PASSED RENAMENX against already existing key PASSED RENAMENX against already existing key (2) PASSED RENAME against non existing source key PASSED RENAME where source and dest key is the same PASSED DEL all keys again (DB 0) PASSED DEL all keys again (DB 1) PASSED MOVE basic usage PASSED MOVE against key existing in the target DB PASSED SET/GET keys in different DBs PASSED Basic LPOP/RPOP PASSED LPOP/RPOP against empty list PASSED LPOP against non list value PASSED Mass LPUSH/LPOP PASSED LRANGE basics PASSED LRANGE inverted indexes PASSED LRANGE out of range indexes including the full list PASSED LRANGE against non existing key PASSED LTRIM basics PASSED LSET PASSED LSET out of range index PASSED LSET against non existing key PASSED LSET against non list value PASSED SADD, SCARD, SISMEMBER, SMEMBERS basics PASSED SADD adding the same element multiple times PASSED SADD against non set PASSED SREM basics PASSED Mass SADD and SINTER with two sets PASSED SINTERSTORE with two sets PASSED SINTER against three sets PASSED SINTERSTORE with three sets PASSED SAVE - make sure there are all the types as values - DB saved on disk PASSED Create a random list . DB 0: 6057 keys in 16384 slots HT. . DB 1: 3 keys in 16 slots HT. . 1 clients connected (0 slaves), 1045147 bytes in use PASSED SORT with BY against the newly created list PASSED SORT direct, numeric, against the newly created list PASSED SORT decreasing sort PASSED SORT speed, sorting 10000 elements list using BY, 100 times Average time to sort: 5.28 milliseconds PASSED SORT speed, sorting 10000 elements list directly, 100 times Average time to sort: 1.53 milliseconds PASSED SORT speed, pseudo-sorting 10000 elements list, BY , 100 times Average time to sort: 0.47 milliseconds PASSED SORT regression for issue #19, sorting floats - DB saved on disk PASSED LREM, remove all the occurrences - DB saved on disk PASSED LREM, remove the first occurrence PASSED LREM, remove non existing element PASSED LREM, starting from tail with negative count - DB saved on disk PASSED LREM, starting from tail with negative count (2) PASSED MGET - DB saved on disk PASSED MGET against non existing key PASSED MGET against non-string key PASSED FLUSHALL - DB saved on disk PASSED 79 tests, 79 passed, 0 failed . Client closed connection . 0 clients connected (0 slaves), 1347660 bytes in use $ kill -15 %1 [1]+ Terminated ./redis-server redis.conf $ vim redis.conf $ tail -1 redis.conf requirepass foobared $ ./redis-server redis.conf & [1] 52518 $ - Server started, Redis version 0.08 - DB loaded from disk - The server is now ready to accept connections on port 6379 . 0 clients connected (0 slaves), 1945 bytes in use $ make test tclsh test-redis.tcl . Accepted 127.0.0.1:54492 DEL all keys to start with a clean DB ERR operation not permitted while executing "::redis::redis_read_reply $fd" (procedure "::redis::__dispatch__" line 13) invoked from within "$r keys *" ("uplevel" body line 2) invoked from within "uplevel 1 $code" (procedure "test" line 4) invoked from within "test {DEL all keys to start with a clean DB} { foreach key [$r keys *] {$r del $key} $r dbsize } {0}" (procedure "main" line 13) invoked from within "main 127.0.0.1 6379" invoked from within "if {[llength $argv] == 0} { main 127.0.0.1 6379 } elseif {[llength $argv] == 1 && [lindex $argv 0] eq {stress}} { stress } else { main [li..." (file "test-redis.tcl" line 630) . Client closed connection make: *** [test] Error 1 . 0 clients connected (0 slaves), 2041 bytes in use . 0 clients connected (0 slaves), 2041 bytes in use $ kill -15 %1 [1]+ Terminated ./redis-server redis.conf $ vim test-redis.tcl $ git diff test-redis.tcl --- redis.c | 22 ++++++++++++++++++++++ redis.conf | 9 +++++++++ test-redis.tcl | 8 ++++++++ 3 files changed, 39 insertions(+) diff --git a/redis.c b/redis.c index 65579c950727..fa62c54cbc58 100644 --- a/redis.c +++ b/redis.c @@ -161,6 +161,7 @@ typedef struct redisClient { time_t lastinteraction; /* time of the last interaction, used for timeout */ int flags; /* REDIS_CLOSE | REDIS_SLAVE | REDIS_MONITOR */ int slaveseldb; /* slave selected db, if this client is a slave */ + int authenticated; /* when requirepass is non-NULL */ } redisClient; struct saveparam { @@ -201,6 +202,7 @@ struct redisServer { char *logfile; char *bindaddr; char *dbfilename; + char *requirepass; int shareobjects; /* Replication related */ int isslave; @@ -263,6 +265,7 @@ static void replicationFeedSlaves(list *slaves, struct redisCommand *cmd, int di static int syncWithMaster(void); static robj *tryObjectSharing(robj *o); +static void authCommand(redisClient *c); static void pingCommand(redisClient *c); static void echoCommand(redisClient *c); static void setCommand(redisClient *c); @@ -349,6 +352,7 @@ static struct redisCommand cmdTable[] = { {"renamenx",renamenxCommand,3,REDIS_CMD_INLINE}, {"keys",keysCommand,2,REDIS_CMD_INLINE}, {"dbsize",dbsizeCommand,1,REDIS_CMD_INLINE}, + {"auth",authCommand,2,REDIS_CMD_INLINE}, {"ping",pingCommand,1,REDIS_CMD_INLINE}, {"echo",echoCommand,2,REDIS_CMD_BULK}, {"save",saveCommand,1,REDIS_CMD_INLINE}, @@ -744,6 +748,7 @@ static void initServerConfig() { server.daemonize = 0; server.pidfile = "/var/run/redis.pid"; server.dbfilename = "dump.rdb"; + server.requirepass = NULL; server.shareobjects = 0; ResetServerSaveParams(); @@ -914,6 +919,8 @@ static void loadServerConfig(char *filename) { else { err = "argument must be 'yes' or 'no'"; goto loaderr; } + } else if (!strcmp(argv[0],"requirepass") && argc == 2) { + server.requirepass = zstrdup(argv[1]); } else if (!strcmp(argv[0],"pidfile") && argc == 2) { server.pidfile = zstrdup(argv[1]); } else { @@ -1092,6 +1099,10 @@ static int processCommand(redisClient *c) { addReplySds(c,sdsnew("-ERR wrong number of arguments\r\n")); resetClient(c); return 1; + } else if (server.requirepass && !c->authenticated && strcmp(c->argv[0]->ptr,"auth")) { + addReplySds(c,sdsnew("-ERR operation not permitted\r\n")); + resetClient(c); + return 1; } else if (cmd->flags & REDIS_CMD_BULK && c->bulklen == -1) { int bulklen = atoi(c->argv[c->argc-1]->ptr); @@ -1303,6 +1314,7 @@ static redisClient *createClient(int fd) { c->sentlen = 0; c->flags = 0; c->lastinteraction = time(NULL); + c->authenticated = 0; if ((c->reply = listCreate()) == NULL) oom("listCreate"); listSetFreeMethod(c->reply,decrRefCount); if (aeCreateFileEvent(server.el, c->fd, AE_READABLE, @@ -1763,6 +1775,16 @@ static int rdbLoad(char *filename) { /*================================== Commands =============================== */ +static void authCommand(redisClient *c) { + if (!strcmp(c->argv[1]->ptr, server.requirepass)) { + c->authenticated = 1; + addReply(c,shared.ok); + } else { + c->authenticated = 0; + addReply(c,shared.err); + } +} + static void pingCommand(redisClient *c) { addReply(c,shared.pong); } diff --git a/redis.conf b/redis.conf index 803e3ec1a714..c1b4297c92f9 100644 --- a/redis.conf +++ b/redis.conf @@ -74,3 +74,12 @@ glueoutputbuf yes # pool so it uses more CPU and can be a bit slower. Usually it's a good # idea. shareobjects no + +# Require clients to issue AUTH before processing any other +# commands. This might be useful in environments in which you do not trust +# others with access to the host running redis-server. +# +# This should stay commented out for backward compatibility and because most +# people do not need auth (e.g. they run their own servers). + +#requirepass foobared diff --git a/test-redis.tcl b/test-redis.tcl index 9c28ad645153..5c4eb571cc30 100644 --- a/test-redis.tcl +++ b/test-redis.tcl @@ -22,6 +22,14 @@ proc main {server port} { set r [redis $server $port] set err "" + # The following AUTH test should be enabled only when requirepass + # is set in redis.conf and redis-server was started with + # redis.conf as the first argument. + + #test {AUTH with requirepass in redis.conf} { + # $r auth foobared + #} {OK} + test {DEL all keys to start with a clean DB} { foreach key [$r keys *] {$r del $key} $r dbsize