From 475f477dab6dfc5e4f018d1ecfb128c37e2c44a0 Mon Sep 17 00:00:00 2001 From: Trond Norbye Date: Wed, 3 Mar 2010 11:50:13 +0100 Subject: [PATCH] Add support for SASL --- clients/client_options.h | 4 +- clients/include.am | 4 + clients/memcat.c | 28 +- clients/memcp.c | 18 +- clients/memdump.c | 17 ++ clients/memflush.c | 26 +- clients/memrm.c | 20 +- clients/utilities.c | 98 +++++- clients/utilities.h | 4 +- configure.ac | 6 + docs/Makefile.am | 41 ++- docs/memcached_sasl.pod | 64 ++++ libmemcached/auto.c | 7 +- libmemcached/configure.h.in | 1 + libmemcached/connect.c | 16 +- libmemcached/constants.h | 3 + libmemcached/delete.c | 6 +- libmemcached/include.am | 8 +- libmemcached/memcached.c | 24 +- libmemcached/memcached.h | 12 + libmemcached/memcached/protocol_binary.h | 18 +- libmemcached/response.c | 94 +++--- libmemcached/sasl.c | 363 +++++++++++++++++++++++ libmemcached/sasl.h | 50 ++++ libmemcached/storage.c | 6 +- libmemcached/strerror.c | 6 + m4/pandora_have_sasl.m4 | 133 +++++++++ m4/protocol_binary.m4 | 5 +- tests/include.am | 34 +-- tests/libmemcached_world.h | 2 +- tests/mem_functions.c | 77 ++++- tests/server.c | 12 +- tests/test.c | 13 + 33 files changed, 1119 insertions(+), 101 deletions(-) create mode 100644 docs/memcached_sasl.pod create mode 100644 libmemcached/sasl.c create mode 100644 libmemcached/sasl.h create mode 100644 m4/pandora_have_sasl.m4 diff --git a/clients/client_options.h b/clients/client_options.h index 955bcd18..9293b9ca 100644 --- a/clients/client_options.h +++ b/clients/client_options.h @@ -35,7 +35,9 @@ typedef enum { OPT_FLUSH, OPT_HASH, OPT_BINARY, - OPT_UDP + OPT_UDP, + OPT_USERNAME, + OPT_PASSWD } memcached_options; #endif /* CLIENT_OPTIONS */ diff --git a/clients/include.am b/clients/include.am index cc734ca7..7b7cbc12 100644 --- a/clients/include.am +++ b/clients/include.am @@ -7,6 +7,10 @@ CLIENTS_LDADDS = \ clients/libutilities.la \ libmemcached/libmemcached.la +if HAVE_SASL +CLIENTS_LDADDS+= $(LIBSASL) +endif + bin_PROGRAMS+= \ clients/memcapable \ clients/memcat \ diff --git a/clients/memcat.c b/clients/memcat.c index f5f7f8f9..3ae82e46 100644 --- a/clients/memcat.c +++ b/clients/memcat.c @@ -31,6 +31,8 @@ static int opt_verbose= 0; static int opt_displayflag= 0; static char *opt_servers= NULL; static char *opt_hash= NULL; +static char *opt_username; +static char *opt_passwd; int main(int argc, char *argv[]) { @@ -68,11 +70,17 @@ int main(int argc, char *argv[]) memcached_behavior_set(memc, MEMCACHED_BEHAVIOR_BINARY_PROTOCOL, (uint64_t)opt_binary); - while (optind < argc) + if (!initialize_sasl(memc, opt_username, opt_passwd)) + { + memcached_free(memc); + return 1; + } + + while (optind < argc) { string= memcached_get(memc, argv[optind], strlen(argv[optind]), &string_length, &flags, &rc); - if (rc == MEMCACHED_SUCCESS) + if (rc == MEMCACHED_SUCCESS) { if (opt_displayflag) { @@ -80,7 +88,7 @@ int main(int argc, char *argv[]) printf("key: %s\nflags: ", argv[optind]); printf("%x\n", flags); } - else + else { if (opt_verbose) printf("key: %s\nflags: %x\nlength: %zu\nvalue: ", @@ -91,7 +99,7 @@ int main(int argc, char *argv[]) } else if (rc != MEMCACHED_NOTFOUND) { - fprintf(stderr, "memcat: %s: memcache error %s", + fprintf(stderr, "memcat: %s: memcache error %s", argv[optind], memcached_strerror(memc, rc)); if (memc->cached_errno) fprintf(stderr, " system error %s", strerror(memc->cached_errno)); @@ -115,6 +123,8 @@ int main(int argc, char *argv[]) if (opt_hash) free(opt_hash); + shutdown_sasl(); + return return_code; } @@ -139,10 +149,12 @@ void options_parse(int argc, char *argv[]) {(OPTIONSTRING)"flag", no_argument, &opt_displayflag, OPT_FLAG}, {(OPTIONSTRING)"hash", required_argument, NULL, OPT_HASH}, {(OPTIONSTRING)"binary", no_argument, NULL, OPT_BINARY}, + {(OPTIONSTRING)"username", required_argument, NULL, OPT_USERNAME}, + {(OPTIONSTRING)"password", required_argument, NULL, OPT_PASSWD}, {0, 0, 0, 0}, }; - while (1) + while (1) { option_rv= getopt_long(argc, argv, "Vhvds:", long_options, &option_index); if (option_rv == -1) break; @@ -171,6 +183,12 @@ void options_parse(int argc, char *argv[]) case OPT_HASH: opt_hash= strdup(optarg); break; + case OPT_USERNAME: + opt_username= optarg; + break; + case OPT_PASSWD: + opt_passwd= optarg; + break; case '?': /* getopt_long already printed an error message. */ exit(1); diff --git a/clients/memcp.c b/clients/memcp.c index dd8aba9a..1dc30631 100644 --- a/clients/memcp.c +++ b/clients/memcp.c @@ -41,6 +41,8 @@ static char *opt_hash= NULL; static int opt_method= OPT_SET; static uint32_t opt_flags= 0; static time_t opt_expires= 0; +static char *opt_username; +static char *opt_passwd; static long strtol_wrapper(const char *nptr, int base, bool *error) { @@ -106,6 +108,11 @@ int main(int argc, char *argv[]) memcached_server_list_free(servers); memcached_behavior_set(memc, MEMCACHED_BEHAVIOR_BINARY_PROTOCOL, (uint64_t)opt_binary); + if (!initialize_sasl(memc, opt_username, opt_passwd)) + { + memcached_free(memc); + return 1; + } while (optind < argc) { @@ -193,6 +200,7 @@ int main(int argc, char *argv[]) free(opt_servers); if (opt_hash) free(opt_hash); + shutdown_sasl(); return return_code; } @@ -221,6 +229,8 @@ static void options_parse(int argc, char *argv[]) {(OPTIONSTRING)"replace", no_argument, NULL, OPT_REPLACE}, {(OPTIONSTRING)"hash", required_argument, NULL, OPT_HASH}, {(OPTIONSTRING)"binary", no_argument, NULL, OPT_BINARY}, + {(OPTIONSTRING)"username", required_argument, NULL, OPT_USERNAME}, + {(OPTIONSTRING)"password", required_argument, NULL, OPT_PASSWD}, {0, 0, 0, 0}, }; @@ -285,7 +295,13 @@ static void options_parse(int argc, char *argv[]) case OPT_HASH: opt_hash= strdup(optarg); break; - case '?': + case OPT_USERNAME: + opt_username= optarg; + break; + case OPT_PASSWD: + opt_passwd= optarg; + break; + case '?': /* getopt_long already printed an error message. */ exit(1); default: diff --git a/clients/memdump.c b/clients/memdump.c index e1854538..40ab430a 100644 --- a/clients/memdump.c +++ b/clients/memdump.c @@ -38,6 +38,8 @@ static int opt_binary=0; static int opt_verbose= 0; static char *opt_servers= NULL; static char *opt_hash= NULL; +static char *opt_username; +static char *opt_passwd; /* Print the keys and counter how many were found */ static memcached_return_t key_printer(const memcached_st *ptr __attribute__((unused)), @@ -85,6 +87,11 @@ int main(int argc, char *argv[]) memcached_server_list_free(servers); memcached_behavior_set(memc, MEMCACHED_BEHAVIOR_BINARY_PROTOCOL, (uint64_t)opt_binary); + if (!initialize_sasl(memc, opt_username, opt_passwd)) + { + memcached_free(memc); + return 1; + } rc= memcached_dump(memc, callbacks, NULL, 1); @@ -103,6 +110,8 @@ int main(int argc, char *argv[]) if (opt_hash) free(opt_hash); + shutdown_sasl(); + return 0; } @@ -120,6 +129,8 @@ static void options_parse(int argc, char *argv[]) {(OPTIONSTRING)"servers", required_argument, NULL, OPT_SERVERS}, {(OPTIONSTRING)"hash", required_argument, NULL, OPT_HASH}, {(OPTIONSTRING)"binary", no_argument, NULL, OPT_BINARY}, + {(OPTIONSTRING)"username", required_argument, NULL, OPT_USERNAME}, + {(OPTIONSTRING)"password", required_argument, NULL, OPT_PASSWD}, {0, 0, 0, 0} }; @@ -154,6 +165,12 @@ static void options_parse(int argc, char *argv[]) case OPT_HASH: opt_hash= strdup(optarg); break; + case OPT_USERNAME: + opt_username= optarg; + break; + case OPT_PASSWD: + opt_passwd= optarg; + break; case '?': /* getopt_long already printed an error message. */ exit(1); diff --git a/clients/memflush.c b/clients/memflush.c index 885aa6c8..4cce8cb0 100644 --- a/clients/memflush.c +++ b/clients/memflush.c @@ -22,6 +22,8 @@ static int opt_binary= 0; static int opt_verbose= 0; static time_t opt_expire= 0; static char *opt_servers= NULL; +static char *opt_username; +static char *opt_passwd; #define PROGRAM_NAME "memflush" #define PROGRAM_DESCRIPTION "Erase all data in a server of memcached servers." @@ -57,11 +59,17 @@ int main(int argc, char *argv[]) memcached_server_list_free(servers); memcached_behavior_set(memc, MEMCACHED_BEHAVIOR_BINARY_PROTOCOL, (uint64_t) opt_binary); - + + if (!initialize_sasl(memc, opt_username, opt_passwd)) + { + memcached_free(memc); + return 1; + } + rc = memcached_flush(memc, opt_expire); - if (rc != MEMCACHED_SUCCESS) + if (rc != MEMCACHED_SUCCESS) { - fprintf(stderr, "memflush: memcache error %s", + fprintf(stderr, "memflush: memcache error %s", memcached_strerror(memc, rc)); if (memc->cached_errno) fprintf(stderr, " system error %s", strerror(memc->cached_errno)); @@ -72,6 +80,8 @@ int main(int argc, char *argv[]) free(opt_servers); + shutdown_sasl(); + return 0; } @@ -92,12 +102,14 @@ void options_parse(int argc, char *argv[]) {(OPTIONSTRING)"servers", required_argument, NULL, OPT_SERVERS}, {(OPTIONSTRING)"expire", required_argument, NULL, OPT_EXPIRE}, {(OPTIONSTRING)"binary", no_argument, NULL, OPT_BINARY}, + {(OPTIONSTRING)"username", required_argument, NULL, OPT_USERNAME}, + {(OPTIONSTRING)"password", required_argument, NULL, OPT_PASSWD}, {0, 0, 0, 0}, }; int option_index= 0; int option_rv; - while (1) + while (1) { option_rv= getopt_long(argc, argv, "Vhvds:", long_options, &option_index); if (option_rv == -1) break; @@ -126,6 +138,12 @@ void options_parse(int argc, char *argv[]) case OPT_EXPIRE: /* --expire */ opt_expire= (time_t)strtoll(optarg, (char **)NULL, 10); break; + case OPT_USERNAME: + opt_username= optarg; + break; + case OPT_PASSWD: + opt_passwd= optarg; + break; case '?': /* getopt_long already printed an error message. */ exit(1); diff --git a/clients/memrm.c b/clients/memrm.c index 81c5f852..dfb2c2fb 100644 --- a/clients/memrm.c +++ b/clients/memrm.c @@ -23,6 +23,8 @@ static int opt_verbose= 0; static time_t opt_expire= 0; static char *opt_servers= NULL; static char *opt_hash= NULL; +static char *opt_username; +static char *opt_passwd; #define PROGRAM_NAME "memrm" #define PROGRAM_DESCRIPTION "Erase a key or set of keys from a memcached cluster." @@ -61,7 +63,13 @@ int main(int argc, char *argv[]) memcached_server_list_free(servers); memcached_behavior_set(memc, MEMCACHED_BEHAVIOR_BINARY_PROTOCOL, (uint64_t) opt_binary); - + + if (!initialize_sasl(memc, opt_username, opt_passwd)) + { + memcached_free(memc); + return 1; + } + while (optind < argc) { if (opt_verbose) @@ -89,6 +97,8 @@ int main(int argc, char *argv[]) if (opt_hash) free(opt_hash); + shutdown_sasl(); + return return_code; } @@ -110,6 +120,8 @@ static void options_parse(int argc, char *argv[]) {(OPTIONSTRING)"expire", required_argument, NULL, OPT_EXPIRE}, {(OPTIONSTRING)"hash", required_argument, NULL, OPT_HASH}, {(OPTIONSTRING)"binary", no_argument, NULL, OPT_BINARY}, + {(OPTIONSTRING)"username", required_argument, NULL, OPT_USERNAME}, + {(OPTIONSTRING)"password", required_argument, NULL, OPT_PASSWD}, {0, 0, 0, 0}, }; int option_index= 0; @@ -147,6 +159,12 @@ static void options_parse(int argc, char *argv[]) case OPT_HASH: opt_hash= strdup(optarg); break; + case OPT_USERNAME: + opt_username= optarg; + break; + case OPT_PASSWD: + opt_passwd= optarg; + break; case '?': /* getopt_long already printed an error message. */ exit(1); diff --git a/clients/utilities.c b/clients/utilities.c index 5d761e3c..ef1e60f3 100644 --- a/clients/utilities.c +++ b/clients/utilities.c @@ -58,6 +58,8 @@ static const char *lookup_help(memcached_options option) case OPT_BINARY: return("Switch to binary protocol."); case OPT_ANALYZE: return("Analyze the provided servers."); case OPT_UDP: return("Use UDP protocol when communicating with server."); + case OPT_USERNAME: return "Username to use for SASL authentication"; + case OPT_PASSWD: return "Password to use for SASL authentication"; default: WATCHPOINT_ASSERT(0); }; @@ -75,12 +77,12 @@ void help_command(const char *command_name, const char *description, printf("\t%s\n\n", description); printf("Current options. A '=' means the option takes a value.\n\n"); - for (x= 0; long_options[x].name; x++) + for (x= 0; long_options[x].name; x++) { const char *help_message; - printf("\t --%s%c\n", long_options[x].name, - long_options[x].has_arg ? '=' : ' '); + printf("\t --%s%c\n", long_options[x].name, + long_options[x].has_arg ? '=' : ' '); if ((help_message= lookup_help(long_options[x].val))) printf("\t\t%s\n", help_message); } @@ -122,3 +124,93 @@ void process_hash_option(memcached_st *memc, char *opt_hash) } } +static char *username; +static char *passwd; + +#ifdef LIBMEMCACHED_WITH_SASL_SUPPORT +static int get_username(void *context, int id, const char **result, + unsigned int *len) +{ + (void)context; + if (!result || (id != SASL_CB_USER && id != SASL_CB_AUTHNAME)) + return SASL_BADPARAM; + + *result= username; + if (len) + *len= (username == NULL) ? 0 : (unsigned int)strlen(username); + + return SASL_OK; +} + +static int get_password(sasl_conn_t *conn, void *context, int id, + sasl_secret_t **psecret) +{ + (void)context; + static sasl_secret_t* x; + + if (!conn || ! psecret || id != SASL_CB_PASS) + return SASL_BADPARAM; + + if (passwd == NULL) + { + *psecret= NULL; + return SASL_OK; + } + + size_t len= strlen(passwd); + x = realloc(x, sizeof(sasl_secret_t) + len); + if (!x) + return SASL_NOMEM; + + x->len = len; + strcpy((void *)x->data, passwd); + + *psecret = x; + return SASL_OK; +} + +/* callbacks we support */ +static sasl_callback_t sasl_callbacks[] = { + { + SASL_CB_USER, &get_username, NULL + }, { + SASL_CB_AUTHNAME, &get_username, NULL + }, { + SASL_CB_PASS, &get_password, NULL + }, { + SASL_CB_LIST_END, NULL, NULL + } +}; +#endif + +bool initialize_sasl(memcached_st *memc, char *user, char *password) +{ +#ifdef LIBMEMCACHED_WITH_SASL_SUPPORT + if (user != NULL && password != NULL) + { + username= user; + passwd= password; + + if (sasl_client_init(NULL) != SASL_OK) + { + fprintf(stderr, "Failed to initialize sasl library!\n"); + return false; + } + memcached_set_sasl_callbacks(memc, sasl_callbacks); + } +#else + (void)memc; + (void)user; + (void)passwd; +#endif + + return true; +} + +void shutdown_sasl(void) +{ +#if LIBMEMCACHED_WITH_SASL_SUPPORT + if (username != NULL || passwd != NULL) + sasl_done(); +#endif +} diff --git a/clients/utilities.h b/clients/utilities.h index f55997a5..162366ab 100644 --- a/clients/utilities.h +++ b/clients/utilities.h @@ -35,7 +35,7 @@ typedef struct memcached_programs_help_st memcached_programs_help_st; -struct memcached_programs_help_st +struct memcached_programs_help_st { char *not_used_yet; }; @@ -48,3 +48,5 @@ void help_command(const char *command_name, const char *description, const struct option *long_options, memcached_programs_help_st *options); void process_hash_option(memcached_st *memc, char *opt_hash); +bool initialize_sasl(memcached_st *memc, char *user, char *password); +void shutdown_sasl(void); diff --git a/configure.ac b/configure.ac index 6f4b0ba8..be8e625f 100644 --- a/configure.ac +++ b/configure.ac @@ -55,6 +55,12 @@ ENABLE_DEPRECATED PANDORA_HAVE_LIBINNODB PANDORA_PRINT_CALLSTACK DETECT_BITFIELD +PANDORA_HAVE_SASL + +dnl The sasl functions should only be visible if we build with sasl support +AS_IF([test "x$ac_cv_sasl" = "xyes"], + [LIBMEMCACHED_WITH_SASL_SUPPORT="#define LIBMEMCACHED_WITH_SASL_SUPPORT 1"]) +AC_SUBST(LIBMEMCACHED_WITH_SASL_SUPPORT) AC_CHECK_HEADERS([atomic.h]) AS_IF([test "x$ac_cv_header_atomic_h" = "xyes"],[ diff --git a/docs/Makefile.am b/docs/Makefile.am index b1d3e6ee..348e9ca6 100644 --- a/docs/Makefile.am +++ b/docs/Makefile.am @@ -89,7 +89,7 @@ POOL_PAGES= \ memcached_pool_create.pop \ memcached_pool_destroy.pop \ memcached_pool_pop.pop \ - memcached_pool_push.pop + memcached_pool_push.pop BUILT_SOURCES += ${POOL_PAGES} RESULT_PAGES= \ @@ -111,14 +111,14 @@ SERVER_PAGES= \ memcached_server_list.pop \ memcached_server_add.pop \ memcached_server_add_unix_socket.pop \ - memcached_server_push.pop + memcached_server_push.pop BUILT_SOURCES += ${SERVER_PAGES} SERVER_ST_PAGES= \ memcached_server_list_free.pop \ memcached_server_list_count.pop \ memcached_server_list_append.pop \ - memcached_servers_parse.pop + memcached_servers_parse.pop BUILT_SOURCES += ${SERVER_ST_PAGES} SET_PAGES= \ @@ -416,6 +416,34 @@ man_MANS = \ memslap.1 \ memstat.1 +if HAVE_SASL +POD_FILES+= memcached_sasl.pod +man_MANS+= \ + memcached_destroy_sasl_auth_data.3 \ + memcached_get_sasl_callbacks.3 \ + memcached_sasl_set_auth_data.3 \ + memcached_set_sasl_callbacks.3 + +HTML_FILES+= \ + memcached_destroy_sasl_auth_data.html \ + memcached_get_sasl_callbacks.html \ + memcached_sasl_set_auth_data.html \ + memcached_set_sasl_callbacks.html + + +SASL_PAGES= \ + memcached_destroy_sasl_auth_data.pop \ + memcached_get_sasl_callbacks.pop \ + memcached_sasl_set_auth_data.pop \ + memcached_set_sasl_callbacks.pop +BUILT_SOURCES += ${SASL_PAGES} + +${SASL_PAGES}: + @rm -f $@ + ln -s ${top_srcdir}/docs/memcached_sasl.pod ${top_builddir}/docs/$@ + +endif + if BUILD_LIBMEMCACHEDUTIL man_MANS+= \ libmemcachedutil.3 \ @@ -428,11 +456,11 @@ man_MANS+= \ endif -${CREATE_PAGES}: +${CREATE_PAGES}: @rm -f $@ ln -s ${top_srcdir}/docs/memcached_create.pod ${top_builddir}/docs/$@ -${SET_PAGES}: +${SET_PAGES}: @rm -f $@ ln -s ${top_srcdir}/docs/memcached_set.pod ${top_builddir}/docs/$@ @@ -488,7 +516,6 @@ ${USER_DATA_PAGES}: @rm -f $@ ln -s ${top_srcdir}/docs/memcached_user_data.pod ${top_builddir}/docs/$@ - ${POOL_PAGES}: @rm -f $@ ln -s ${top_srcdir}/docs/memcached_pool.pod ${top_builddir}/docs/$@ @@ -511,7 +538,7 @@ test-docs: $(POD_FILES) html-local: html-pages html-index -html-pages: $(HTML_FILES) +html-pages: $(HTML_FILES) html-index: html-pages perl make_index.pl *.html > index.html diff --git a/docs/memcached_sasl.pod b/docs/memcached_sasl.pod new file mode 100644 index 00000000..0d182c96 --- /dev/null +++ b/docs/memcached_sasl.pod @@ -0,0 +1,64 @@ +=head1 NAME + +memcached_set_sasl_callbacks, memcached_get_sasl_callbacks, +memcached_sasl_set_auth_data, memcached_destroy_sasl_auth_data - SASL support + +=head1 LIBRARY + +C Client Library for memcached (libmemcached, -lmemcached) + +=head1 SYNOPSIS + + #include + + void memcached_set_sasl_callbacks(memcached_st *ptr, + const sasl_callback_t *callbacks) + + const sasl_callback_t *memcached_get_sasl_callbacks(memcached_st *ptr) + + memcached_return memcached_set_sasl_auth_data(memcached_st *ptr, + const char *username, + const char *password) + memcached_return memcached_destroy_sasl_auth_data(memcached_st *ptr) + + +=head1 DESCRIPTION + +libmemcached(3) allows you to plug in your own callbacks function used by +libsasl to perform SASL authentication. + +Please note that SASL requires the memcached binary protocol, and you have +to specify the callbacks before you connect to the server. + +memcached_set_sasl_auth_data() is a helper function for you defining +the basic functionality for you, but it will store the username and password +in memory. If you choose to use this method you have to call +memcached_destroy_sasl_auth_data before calling memcached_free to avoid +a memory leak. You should NOT call memcached_destroy_sasl_auth_data if you +specify your own callback function with memcached_set_sasl_callbacks(). + +You as a client user have to initialize libsasl by using sasl_client_init +before enabling it in libmemcached, and you have to shut down libsasl by +calling sasl_done() when you are done using SASL from libmemcached. + + +=head1 RETURN + +memcached_get_sasl_callbacks() returns the callbacks currently used +by this memcached handle. +memcached_get_sasl_set_auth_data() returns MEMCACHED_SUCCESS upon success. + +=head1 HOME + +To find out more information please check: +L + +=head1 AUTHOR + +Trond Norbye, Etrond.norbye@gmail.comE + +=head1 SEE ALSO + +memcached(1) libmemcached(3) + +=cut diff --git a/libmemcached/auto.c b/libmemcached/auto.c index c36cd92f..fde81d47 100644 --- a/libmemcached/auto.c +++ b/libmemcached/auto.c @@ -111,12 +111,13 @@ static memcached_return_t binary_incr_decr(memcached_st *ptr, uint8_t cmd, request.message.body.initial= htonll(initial); request.message.body.expiration= htonl((uint32_t) expiration); - if ((memcached_do(instance, request.bytes, - sizeof(request.bytes), false) != MEMCACHED_SUCCESS) || + memcached_return_t rc; + if (((rc= memcached_do(instance, request.bytes, + sizeof(request.bytes), false)) != MEMCACHED_SUCCESS) || (memcached_io_write(instance, key, key_length, true) == -1)) { memcached_io_reset(instance); - return MEMCACHED_WRITE_FAILURE; + return (rc == MEMCACHED_SUCCESS) ? MEMCACHED_WRITE_FAILURE : rc; } if (no_reply) diff --git a/libmemcached/configure.h.in b/libmemcached/configure.h.in index 064f3b73..c030d305 100644 --- a/libmemcached/configure.h.in +++ b/libmemcached/configure.h.in @@ -18,6 +18,7 @@ extern "C" { @MEMCACHED_BITFIELD@ @DEPRECATED@ +@LIBMEMCACHED_WITH_SASL_SUPPORT@ #define LIBMEMCACHED_VERSION_STRING "@VERSION@" #define LIBMEMCACHED_VERSION_HEX @PANDORA_HEX_VERSION@ diff --git a/libmemcached/connect.c b/libmemcached/connect.c index 4b8939c1..f3832fae 100644 --- a/libmemcached/connect.c +++ b/libmemcached/connect.c @@ -282,6 +282,20 @@ static memcached_return_t network_connect(memcached_server_st *ptr) } } +#ifdef LIBMEMCACHED_WITH_SASL_SUPPORT + if (ptr->fd != -1 && ptr->root->sasl.callbacks != NULL) + { + memcached_return rc= memcached_sasl_authenticate_connection(ptr); + if (rc != MEMCACHED_SUCCESS) + { + (void)close(ptr->fd); + ptr->fd= -1; + return rc; + } + } +#endif + + if (ptr->fd != -1) { ptr->server_failure_counter= 0; @@ -368,7 +382,7 @@ memcached_return_t memcached_connect(memcached_server_write_instance_st ptr) WATCHPOINT_ASSERT(0); } - unlikely ( rc != MEMCACHED_SUCCESS) + unlikely ( rc != MEMCACHED_SUCCESS) { //@todo create interface around last_discontected_server memcached_st *root= (memcached_st *)ptr->root; diff --git a/libmemcached/constants.h b/libmemcached/constants.h index f05b03eb..ef8229af 100644 --- a/libmemcached/constants.h +++ b/libmemcached/constants.h @@ -69,6 +69,9 @@ typedef enum { MEMCACHED_E2BIG, MEMCACHED_INVALID_ARGUMENTS, MEMCACHED_KEY_TOO_BIG, + MEMCACHED_AUTH_PROBLEM, + MEMCACHED_AUTH_FAILURE, + MEMCACHED_AUTH_CONTINUE, MEMCACHED_MAXIMUM_RETURN /* Always add new error code before */ } memcached_return_t; diff --git a/libmemcached/delete.c b/libmemcached/delete.c index 7fd1f2aa..da91f703 100644 --- a/libmemcached/delete.c +++ b/libmemcached/delete.c @@ -173,13 +173,13 @@ static inline memcached_return_t binary_delete(memcached_st *ptr, memcached_return_t rc= MEMCACHED_SUCCESS; - if ((memcached_do(instance, request.bytes, - sizeof(request.bytes), false) != MEMCACHED_SUCCESS) || + if (((rc= memcached_do(instance, request.bytes, + sizeof(request.bytes), false)) != MEMCACHED_SUCCESS) || (memcached_io_write(instance, key, key_length, flush) == -1)) { memcached_io_reset(instance); - rc= MEMCACHED_WRITE_FAILURE; + rc= (rc == MEMCACHED_SUCCESS) ? MEMCACHED_WRITE_FAILURE : rc; } unlikely (ptr->number_of_replicas > 0) diff --git a/libmemcached/include.am b/libmemcached/include.am index 737c5e12..dc125bd7 100644 --- a/libmemcached/include.am +++ b/libmemcached/include.am @@ -44,6 +44,7 @@ nobase_include_HEADERS+= \ libmemcached/protocol_handler.h \ libmemcached/quit.h \ libmemcached/result.h \ + libmemcached/sasl.h \ libmemcached/server.h \ libmemcached/server_list.h \ libmemcached/stats.h \ @@ -64,7 +65,7 @@ libmemcached_libmemcachedprotocol_la_SOURCES = \ libmemcached/protocol/pedantic.c \ libmemcached/protocol/protocol_handler.c -libmemcached_libmemcachedprotocol_la_LDFLAGS= ${AM_LDFLAGS} -version-info 0:0:0 +libmemcached_libmemcachedprotocol_la_LDFLAGS= ${AM_LDFLAGS} -version-info 0:0:0 noinst_LTLIBRARIES+= \ libmemcached/libmemcachedcallbacks.la @@ -138,6 +139,11 @@ libmemcached_libmemcachedprotocol_la_LIBADD=libmemcached/libbyteorder.la libmemcached_libmemcachedprotocol_la_DEPENDENCIES=libmemcached/libbyteorder.la endif +if HAVE_SASL +libmemcached_libmemcached_la_LDFLAGS+= $(LIBSASL) +libmemcached_libmemcached_la_SOURCES += libmemcached/sasl.c +endif + if HAVE_DTRACE BUILT_SOURCES+= libmemcached/dtrace_probes.h libmemcached_libmemcached_la_SOURCES += libmemcached/libmemcached_probes.d diff --git a/libmemcached/memcached.c b/libmemcached/memcached.c index 4e0308a0..2d28830f 100644 --- a/libmemcached/memcached.c +++ b/libmemcached/memcached.c @@ -5,7 +5,7 @@ * Use and distribution licensed under the BSD license. See * the COPYING file in the parent directory for full text. * - * Summary: + * Summary: * */ @@ -88,6 +88,10 @@ static inline bool _memcached_init(memcached_st *self) self->get_key_failure= NULL; self->delete_trigger= NULL; self->callbacks= NULL; +#ifdef LIBMEMCACHED_WITH_SASL_SUPPORT + self->sasl.callbacks= NULL; + self->sasl.is_allocated= false; +#endif return true; } @@ -155,6 +159,13 @@ void memcached_free(memcached_st *ptr) if (ptr->continuum) libmemcached_free(ptr, ptr->continuum); +#ifdef LIBMEMCACHED_WITH_SASL_SUPPORT + if (ptr->sasl.callbacks != NULL) + { + memcached_destroy_sasl_auth_data(ptr); + } +#endif + if (memcached_is_allocated(ptr)) { libmemcached_free(ptr, ptr); @@ -243,6 +254,17 @@ memcached_st *memcached_clone(memcached_st *clone, const memcached_st *source) new_clone->prefix_key_length= source->prefix_key_length; } +#ifdef LIBMEMCACHED_WITH_SASL_SUPPORT + if (source->sasl.callbacks) + { + if (memcached_clone_sasl(new_clone, source) != MEMCACHED_SUCCESS) + { + memcached_free(new_clone); + return NULL; + } + } +#endif + rc= run_distribution(new_clone); if (rc != MEMCACHED_SUCCESS) diff --git a/libmemcached/memcached.h b/libmemcached/memcached.h index 30823a87..5aca6db1 100644 --- a/libmemcached/memcached.h +++ b/libmemcached/memcached.h @@ -53,6 +53,7 @@ #include #include #include +#include struct memcached_st { /** @@ -121,6 +122,17 @@ struct memcached_st { memcached_trigger_key_fn get_key_failure; memcached_trigger_delete_key_fn delete_trigger; memcached_callback_st *callbacks; +#ifdef LIBMEMCACHED_WITH_SASL_SUPPORT + struct { + const sasl_callback_t *callbacks; + /* + ** Did we allocate data inside the callbacks, or did the user + ** supply that. + */ + bool is_allocated MEMCACHED_BITFIELD; + } sasl; + +#endif char prefix_key[MEMCACHED_PREFIX_KEY_MAX_SIZE]; struct { bool is_allocated MEMCACHED_BITFIELD; diff --git a/libmemcached/memcached/protocol_binary.h b/libmemcached/memcached/protocol_binary.h index e5226b5b..ecdc52e3 100644 --- a/libmemcached/memcached/protocol_binary.h +++ b/libmemcached/memcached/protocol_binary.h @@ -29,13 +29,16 @@ * * Copy: See Copyright for the status of this software. * - * Author: Trond Norbye + * Author: Trond Norbye */ #ifndef PROTOCOL_BINARY_H #define PROTOCOL_BINARY_H -#include +/** + * \addtogroup Protocol + * @{ + */ /** * This file contains definitions of the constants and packet formats @@ -69,11 +72,14 @@ extern "C" PROTOCOL_BINARY_RESPONSE_EINVAL = 0x04, PROTOCOL_BINARY_RESPONSE_NOT_STORED = 0x05, PROTOCOL_BINARY_RESPONSE_DELTA_BADVAL = 0x06, + PROTOCOL_BINARY_RESPONSE_AUTH_ERROR = 0x20, + PROTOCOL_BINARY_RESPONSE_AUTH_CONTINUE = 0x21, PROTOCOL_BINARY_RESPONSE_UNKNOWN_COMMAND = 0x81, PROTOCOL_BINARY_RESPONSE_ENOMEM = 0x82, PROTOCOL_BINARY_RESPONSE_PAUSE = 0xfe00, PROTOCOL_BINARY_RESPONSE_EIO = 0xff00 + } protocol_binary_response_status; /** @@ -109,6 +115,10 @@ extern "C" PROTOCOL_BINARY_CMD_APPENDQ = 0x19, PROTOCOL_BINARY_CMD_PREPENDQ = 0x1a, + PROTOCOL_BINARY_CMD_SASL_LIST_MECHS = 0x20, + PROTOCOL_BINARY_CMD_SASL_AUTH = 0x21, + PROTOCOL_BINARY_CMD_SASL_STEP = 0x22, + /* These commands are used for range operations and exist within * this header for use in other projects. Range operations are * not expected to be implemented in the memcached server itself. @@ -415,6 +425,10 @@ extern "C" typedef protocol_binary_request_rangeop protocol_binary_request_rdecr; typedef protocol_binary_request_rangeop protocol_binary_request_rdecrq; + /** + * @} + */ + #ifdef __cplusplus } #endif diff --git a/libmemcached/response.c b/libmemcached/response.c index c14dd1ff..0b8ebf6f 100644 --- a/libmemcached/response.c +++ b/libmemcached/response.c @@ -45,7 +45,7 @@ memcached_return_t memcached_read_one_response(memcached_server_write_instance_s return rc; } -memcached_return_t memcached_response(memcached_server_write_instance_st ptr, +memcached_return_t memcached_response(memcached_server_write_instance_st ptr, char *buffer, size_t buffer_length, memcached_result_st *result) { @@ -58,7 +58,7 @@ memcached_return_t memcached_response(memcached_server_write_instance_st ptr, /* * The previous implementation purged all pending requests and just * returned the last one. Purge all pending messages to ensure backwards - * compatibility. + * compatibility. */ if (ptr->root->flags.binary_protocol == false) while (memcached_server_response_count(ptr) > 1) @@ -68,10 +68,10 @@ memcached_return_t memcached_response(memcached_server_write_instance_st ptr, unlikely (rc != MEMCACHED_END && rc != MEMCACHED_STORED && rc != MEMCACHED_SUCCESS && - rc != MEMCACHED_STAT && + rc != MEMCACHED_STAT && rc != MEMCACHED_DELETED && rc != MEMCACHED_NOTFOUND && - rc != MEMCACHED_NOTSTORED && + rc != MEMCACHED_NOTSTORED && rc != MEMCACHED_DATA_EXISTS) return rc; } @@ -176,8 +176,8 @@ static memcached_return_t textual_value_fetch(memcached_server_write_instance_st } value_ptr= memcached_string_value_mutable(&result->value); - /* - We read the \r\n into the string since not doing so is more + /* + We read the \r\n into the string since not doing so is more cycles then the waster of memory to do so. We are null terminating through, which will most likely make @@ -246,21 +246,21 @@ static memcached_return_t textual_read_one_response(memcached_server_write_insta memcached_server_response_increment(ptr); return MEMCACHED_STAT; } - else if (buffer[1] == 'E') /* SERVER_ERROR */ + else if (buffer[1] == 'E') /* SERVER_ERROR */ { char *rel_ptr; char *startptr= buffer + 13, *endptr= startptr; while (*endptr != '\r' && *endptr != '\n') endptr++; - /* + /* Yes, we could make this "efficent" but to do that we would need to maintain more state for the size of the buffer. Why waste memory in the struct, which is important, for something that rarely should happen? */ rel_ptr= (char *)libmemcached_realloc(ptr->root, - ptr->cached_server_error, + ptr->cached_server_error, (size_t) (endptr - startptr + 1)); if (rel_ptr == NULL) @@ -333,11 +333,11 @@ static memcached_return_t binary_read_one_response(memcached_server_write_instan { protocol_binary_response_header header; - unlikely (memcached_safe_read(ptr, &header.bytes, + unlikely (memcached_safe_read(ptr, &header.bytes, sizeof(header.bytes)) != MEMCACHED_SUCCESS) return MEMCACHED_UNKNOWN_READ_FAILURE; - unlikely (header.response.magic != PROTOCOL_BINARY_RES) + unlikely (header.response.magic != PROTOCOL_BINARY_RES) return MEMCACHED_PROTOCOL_ERROR; /* @@ -349,7 +349,8 @@ static memcached_return_t binary_read_one_response(memcached_server_write_instan header.response.cas= ntohll(header.response.cas); uint32_t bodylen= header.response.bodylen; - if (header.response.status == 0) + if (header.response.status == PROTOCOL_BINARY_RESPONSE_SUCCESS || + header.response.status == PROTOCOL_BINARY_RESPONSE_AUTH_CONTINUE) { switch (header.response.opcode) { @@ -357,8 +358,8 @@ static memcached_return_t binary_read_one_response(memcached_server_write_instan /* * We didn't increment the response counter for the GETKQ packet * (only the final NOOP), so we need to increment the counter again. - */ - memcached_server_response_increment(ptr); + */ + memcached_server_response_increment(ptr); /* FALLTHROUGH */ case PROTOCOL_BINARY_CMD_GETK: { @@ -374,36 +375,37 @@ static memcached_return_t binary_read_one_response(memcached_server_write_instan bodylen -= header.response.extlen; result->key_length= keylen; - if (memcached_safe_read(ptr, result->item_key, keylen) != MEMCACHED_SUCCESS) + if (memcached_safe_read(ptr, result->item_key, keylen) != MEMCACHED_SUCCESS) return MEMCACHED_UNKNOWN_READ_FAILURE; bodylen -= keylen; if (memcached_string_check(&result->value, - bodylen) != MEMCACHED_SUCCESS) + bodylen) != MEMCACHED_SUCCESS) return MEMCACHED_MEMORY_ALLOCATION_FAILURE; char *vptr= memcached_string_value_mutable(&result->value); - if (memcached_safe_read(ptr, vptr, bodylen) != MEMCACHED_SUCCESS) + if (memcached_safe_read(ptr, vptr, bodylen) != MEMCACHED_SUCCESS) return MEMCACHED_UNKNOWN_READ_FAILURE; - memcached_string_set_length(&result->value, bodylen); - } + memcached_string_set_length(&result->value, bodylen); + } break; case PROTOCOL_BINARY_CMD_INCREMENT: case PROTOCOL_BINARY_CMD_DECREMENT: { - if (bodylen != sizeof(uint64_t) || buffer_length != sizeof(uint64_t)) + if (bodylen != sizeof(uint64_t) || buffer_length != sizeof(uint64_t)) return MEMCACHED_PROTOCOL_ERROR; WATCHPOINT_ASSERT(bodylen == buffer_length); uint64_t val; - if (memcached_safe_read(ptr, &val, sizeof(val)) != MEMCACHED_SUCCESS) + if (memcached_safe_read(ptr, &val, sizeof(val)) != MEMCACHED_SUCCESS) return MEMCACHED_UNKNOWN_READ_FAILURE; val= ntohll(val); memcpy(buffer, &val, sizeof(val)); - } + } break; + case PROTOCOL_BINARY_CMD_SASL_LIST_MECHS: case PROTOCOL_BINARY_CMD_VERSION: { memset(buffer, 0, buffer_length); @@ -412,7 +414,7 @@ static memcached_return_t binary_read_one_response(memcached_server_write_instan return MEMCACHED_UNKNOWN_READ_FAILURE; else if (memcached_safe_read(ptr, buffer, bodylen) != MEMCACHED_SUCCESS) return MEMCACHED_UNKNOWN_READ_FAILURE; - } + } break; case PROTOCOL_BINARY_CMD_FLUSH: case PROTOCOL_BINARY_CMD_QUIT: @@ -425,7 +427,7 @@ static memcached_return_t binary_read_one_response(memcached_server_write_instan { WATCHPOINT_ASSERT(bodylen == 0); return MEMCACHED_SUCCESS; - } + } case PROTOCOL_BINARY_CMD_NOOP: { WATCHPOINT_ASSERT(bodylen == 0); @@ -438,30 +440,48 @@ static memcached_return_t binary_read_one_response(memcached_server_write_instan else if (bodylen + 1 > buffer_length) /* not enough space in buffer.. should not happen... */ return MEMCACHED_UNKNOWN_READ_FAILURE; - else + else { - size_t keylen= header.response.keylen; + size_t keylen= header.response.keylen; memset(buffer, 0, buffer_length); if (memcached_safe_read(ptr, buffer, keylen) != MEMCACHED_SUCCESS || - memcached_safe_read(ptr, buffer + keylen + 1, + memcached_safe_read(ptr, buffer + keylen + 1, bodylen - keylen) != MEMCACHED_SUCCESS) return MEMCACHED_UNKNOWN_READ_FAILURE; } - } + } + break; + + case PROTOCOL_BINARY_CMD_SASL_AUTH: + case PROTOCOL_BINARY_CMD_SASL_STEP: + { + memcached_result_reset(result); + result->item_cas= header.response.cas; + + if (memcached_string_check(&result->value, + bodylen) != MEMCACHED_SUCCESS) + return MEMCACHED_MEMORY_ALLOCATION_FAILURE; + + char *vptr= memcached_string_value_mutable(&result->value); + if (memcached_safe_read(ptr, vptr, bodylen) != MEMCACHED_SUCCESS) + return MEMCACHED_UNKNOWN_READ_FAILURE; + + memcached_string_set_length(&result->value, bodylen); + } break; default: { /* Command not implemented yet! */ WATCHPOINT_ASSERT(0); return MEMCACHED_PROTOCOL_ERROR; - } + } } - } - else if (header.response.bodylen) + } + else if (header.response.bodylen) { /* What should I do with the error message??? just discard it for now */ char hole[SMALL_STRING_LEN]; - while (bodylen > 0) + while (bodylen > 0) { size_t nr= (bodylen > SMALL_STRING_LEN) ? SMALL_STRING_LEN : bodylen; if (memcached_safe_read(ptr, hole, nr) != MEMCACHED_SUCCESS) @@ -487,8 +507,8 @@ static memcached_return_t binary_read_one_response(memcached_server_write_instan } memcached_return_t rc= MEMCACHED_SUCCESS; - unlikely(header.response.status != 0) - switch (header.response.status) + unlikely(header.response.status != 0) + switch (header.response.status) { case PROTOCOL_BINARY_RESPONSE_KEY_ENOENT: rc= MEMCACHED_NOTFOUND; @@ -505,6 +525,12 @@ static memcached_return_t binary_read_one_response(memcached_server_write_instan case PROTOCOL_BINARY_RESPONSE_ENOMEM: rc= MEMCACHED_MEMORY_ALLOCATION_FAILURE; break; + case PROTOCOL_BINARY_RESPONSE_AUTH_CONTINUE: + rc= MEMCACHED_AUTH_CONTINUE; + break; + case PROTOCOL_BINARY_RESPONSE_AUTH_ERROR: + rc= MEMCACHED_AUTH_FAILURE; + break; case PROTOCOL_BINARY_RESPONSE_EINVAL: case PROTOCOL_BINARY_RESPONSE_UNKNOWN_COMMAND: default: diff --git a/libmemcached/sasl.c b/libmemcached/sasl.c new file mode 100644 index 00000000..9b4533b3 --- /dev/null +++ b/libmemcached/sasl.c @@ -0,0 +1,363 @@ +/* LibMemcached + * Copyright (C) 2006-2010 Brian Aker + * All rights reserved. + * + * Use and distribution licensed under the BSD license. See + * the COPYING file in the parent directory for full text. + * + * Summary: interface for memcached server + * Description: main include file for libmemcached + * + */ +#include "common.h" + +void memcached_set_sasl_callbacks(memcached_st *ptr, + const sasl_callback_t *callbacks) +{ + ptr->sasl.callbacks= callbacks; + ptr->sasl.is_allocated= false; +} + +const sasl_callback_t *memcached_get_sasl_callbacks(memcached_st *ptr) +{ + return ptr->sasl.callbacks; +} + +/** + * Resolve the names for both ends of a connection + * @param fd socket to check + * @param laddr local address (out) + * @param raddr remote address (out) + * @return true on success false otherwise (errno contains more info) + */ +static bool resolve_names(int fd, char *laddr, char *raddr) +{ + char host[NI_MAXHOST]; + char port[NI_MAXSERV]; + struct sockaddr_storage saddr; + socklen_t salen= sizeof(saddr); + + if ((getsockname(fd, (struct sockaddr *)&saddr, &salen) < 0) || + (getnameinfo((struct sockaddr *)&saddr, salen, host, sizeof(host), + port, sizeof(port), NI_NUMERICHOST | NI_NUMERICSERV) < 0)) + { + return false; + } + + (void)sprintf(laddr, "%s;%s", host, port); + salen= sizeof(saddr); + + if ((getpeername(fd, (struct sockaddr *)&saddr, &salen) < 0) || + (getnameinfo((struct sockaddr *)&saddr, salen, host, sizeof(host), + port, sizeof(port), NI_NUMERICHOST | NI_NUMERICSERV) < 0)) + { + return false; + } + + (void)sprintf(raddr, "%s;%s", host, port); + + return true; +} + +memcached_return_t memcached_sasl_authenticate_connection(memcached_server_st *server) +{ + memcached_return_t rc; + + /* SANITY CHECK: SASL can only be used with the binary protocol */ + unlikely (!server->root->flags.binary_protocol) + return MEMCACHED_FAILURE; + + /* Try to get the supported mech from the server. Servers without SASL + * support will return UNKNOWN COMMAND, so we can just treat that + * as authenticated + */ + protocol_binary_request_no_extras request= { + .message.header.request= { + .magic= PROTOCOL_BINARY_REQ, + .opcode= PROTOCOL_BINARY_CMD_SASL_LIST_MECHS + } + }; + + if (memcached_io_write(server, request.bytes, + sizeof(request.bytes), 1) != sizeof(request.bytes)) + { + return MEMCACHED_WRITE_FAILURE; + } + + memcached_server_response_increment(server); + + char mech[MEMCACHED_MAX_BUFFER]; + rc= memcached_response(server, mech, sizeof(mech), NULL); + if (rc != MEMCACHED_SUCCESS) + { + if (rc == MEMCACHED_PROTOCOL_ERROR) + { + /* If the server doesn't support SASL it will return PROTOCOL_ERROR. + * This error may also be returned for other errors, but let's assume + * that the server don't support SASL and treat it as success and + * let the client fail with the next operation if the error was + * caused by another problem.... + */ + rc= MEMCACHED_SUCCESS; + } + + return rc; + } + + /* set ip addresses */ + char laddr[NI_MAXHOST + NI_MAXSERV]; + char raddr[NI_MAXHOST + NI_MAXSERV]; + + unlikely (!resolve_names(server->fd, laddr, raddr)) + { + server->cached_errno= errno; + return MEMCACHED_ERRNO; + } + + sasl_conn_t *conn; + int ret= sasl_client_new("memcached", server->hostname, laddr, raddr, + server->root->sasl.callbacks, 0, &conn); + if (ret != SASL_OK) + { + return MEMCACHED_AUTH_PROBLEM; + } + + const char *data; + const char *chosenmech; + unsigned int len; + ret= sasl_client_start(conn, mech, NULL, &data, &len, &chosenmech); + + if (ret != SASL_OK && ret != SASL_CONTINUE) + { + rc= MEMCACHED_AUTH_PROBLEM; + goto end; + } + + uint16_t keylen= (uint16_t)strlen(chosenmech); + request.message.header.request.opcode= PROTOCOL_BINARY_CMD_SASL_AUTH; + request.message.header.request.keylen= htons(keylen); + request.message.header.request.bodylen= htonl(len + keylen); + + do { + /* send the packet */ + if (memcached_io_write(server, request.bytes, + sizeof(request.bytes), 0) != sizeof(request.bytes) || + memcached_io_write(server, chosenmech, keylen, 0) != keylen || + memcached_io_write(server, data, len, 1) != (ssize_t)len) + { + rc= MEMCACHED_WRITE_FAILURE; + goto end; + } + memcached_server_response_increment(server); + + /* read the response */ + rc= memcached_response(server, NULL, 0, NULL); + if (rc != MEMCACHED_AUTH_CONTINUE) + { + goto end; + } + + ret= sasl_client_step(conn, memcached_result_value(&server->root->result), + (unsigned int)memcached_result_length(&server->root->result), + NULL, &data, &len); + + if (ret != SASL_OK && ret != SASL_CONTINUE) + { + rc= MEMCACHED_AUTH_PROBLEM; + goto end; + } + + request.message.header.request.opcode= PROTOCOL_BINARY_CMD_SASL_STEP; + request.message.header.request.bodylen= htonl(len + keylen); + } while (true); + +end: + /* Release resources */ + sasl_dispose(&conn); + + return rc; +} + +static int get_username(void *context, int id, const char **result, + unsigned int *len) +{ + if (!context || !result || (id != SASL_CB_USER && id != SASL_CB_AUTHNAME)) + { + return SASL_BADPARAM; + } + + *result= context; + if (len) + { + *len= (unsigned int)strlen(*result); + } + + return SASL_OK; +} + +static int get_password(sasl_conn_t *conn, void *context, int id, + sasl_secret_t **psecret) +{ + if (!conn || ! psecret || id != SASL_CB_PASS) + { + return SASL_BADPARAM; + } + + *psecret= context; + + return SASL_OK; +} + +memcached_return_t memcached_set_sasl_auth_data(memcached_st *ptr, + const char *username, + const char *password) +{ + if (ptr == NULL || username == NULL || + password == NULL || ptr->sasl.callbacks != NULL) + { + return MEMCACHED_FAILURE; + } + + sasl_callback_t *cb= libmemcached_calloc(ptr, 4, sizeof(sasl_callback_t)); + char *name= libmemcached_malloc(ptr, strlen(username) + 1); + sasl_secret_t *secret= libmemcached_malloc(ptr, strlen(password) + 1 + sizeof(*secret)) +; + if (cb == NULL || name == NULL || secret == NULL) + { + libmemcached_free(ptr, cb); + libmemcached_free(ptr, name); + libmemcached_free(ptr, secret); + return MEMCACHED_MEMORY_ALLOCATION_FAILURE; + } + + secret->len= strlen(password); + strcpy((void*)secret->data, password); + + cb[0].id= SASL_CB_USER; + cb[0].proc= get_username; + cb[0].context= strcpy(name, username); + cb[1].id= SASL_CB_AUTHNAME; + cb[1].proc= get_username; + cb[1].context= name; + cb[2].id= SASL_CB_PASS; + cb[2].proc= get_password; + cb[2].context= secret; + cb[3].id= SASL_CB_LIST_END; + + ptr->sasl.callbacks= cb; + ptr->sasl.is_allocated= true; + + return MEMCACHED_SUCCESS; +} + +memcached_return_t memcached_destroy_sasl_auth_data(memcached_st *ptr) +{ + if (ptr == NULL || ptr->sasl.callbacks == NULL) + { + return MEMCACHED_FAILURE; + } + + if (ptr->sasl.is_allocated) + { + libmemcached_free(ptr, ptr->sasl.callbacks[0].context); + libmemcached_free(ptr, ptr->sasl.callbacks[2].context); + libmemcached_free(ptr, (void*)ptr->sasl.callbacks); + ptr->sasl.is_allocated= false; + } + + ptr->sasl.callbacks= NULL; + + return MEMCACHED_SUCCESS; +} + +memcached_return_t memcached_clone_sasl(memcached_st *clone, const memcached_st *source) +{ + /* Hopefully we are using our own callback mechanisms.. */ + if (source->sasl.callbacks[0].id == SASL_CB_USER && + source->sasl.callbacks[0].proc == get_username && + source->sasl.callbacks[1].id == SASL_CB_AUTHNAME && + source->sasl.callbacks[1].proc == get_username && + source->sasl.callbacks[2].id == SASL_CB_PASS && + source->sasl.callbacks[2].proc == get_password && + source->sasl.callbacks[3].id == SASL_CB_LIST_END) + { + sasl_secret_t *secret= source->sasl.callbacks[2].context; + return memcached_set_sasl_auth_data(clone, + source->sasl.callbacks[0].context, + (const char*)secret->data); + } + + /* + * But we're not. It may work if we know what the user tries to pass + * into the list, but if we don't know the ID we don't know how to handle + * the context... + */ + size_t total= 0; + + while (source->sasl.callbacks[total].id != SASL_CB_LIST_END) { + switch (source->sasl.callbacks[total].id) + { + case SASL_CB_USER: + case SASL_CB_AUTHNAME: + case SASL_CB_PASS: + break; + default: + /* I don't know how to deal with this... */ + return MEMCACHED_NOT_SUPPORTED; + } + + ++total; + } + + sasl_callback_t *cb= libmemcached_calloc(clone, total + 1, + sizeof(sasl_callback_t)); + if (cb == NULL) + { + return MEMCACHED_MEMORY_ALLOCATION_FAILURE; + } + memcpy(cb, source->sasl.callbacks, (total + 1) * sizeof(sasl_callback_t)); + + /* Now update the context... */ + for (size_t x= 0; x < total; ++x) + { + if (cb[x].id == SASL_CB_USER || cb[x].id == SASL_CB_AUTHNAME) + { + cb[x].context= libmemcached_malloc(clone, strlen(source->sasl.callbacks[x].context)); + if (cb[x].context == NULL) + { + /* Failed to allocate memory, clean up previously allocated memory */ + for (size_t y= 0; y < x; ++y) + { + libmemcached_free(clone, clone->sasl.callbacks[y].context); + } + + libmemcached_free(clone, cb); + return MEMCACHED_MEMORY_ALLOCATION_FAILURE; + } + strcpy(cb[x].context, source->sasl.callbacks[x].context); + } + else + { + sasl_secret_t *src = source->sasl.callbacks[x].context; + sasl_secret_t *n = libmemcached_malloc(clone, src->len + 1 + sizeof(*n)); + if (n == NULL) + { + /* Failed to allocate memory, clean up previously allocated memory */ + for (size_t y= 0; y < x; ++y) + { + libmemcached_free(clone, clone->sasl.callbacks[y].context); + } + + libmemcached_free(clone, cb); + return MEMCACHED_MEMORY_ALLOCATION_FAILURE; + } + memcpy(n, src, src->len + 1 + sizeof(*n)); + cb[x].context= n; + } + } + + clone->sasl.callbacks= cb; + clone->sasl.is_allocated= true; + + return MEMCACHED_SUCCESS; +} diff --git a/libmemcached/sasl.h b/libmemcached/sasl.h new file mode 100644 index 00000000..6867f079 --- /dev/null +++ b/libmemcached/sasl.h @@ -0,0 +1,50 @@ +/* LibMemcached + * Copyright (C) 2006-2010 Brian Aker + * All rights reserved. + * + * Use and distribution licensed under the BSD license. See + * the COPYING file in the parent directory for full text. + * + * Summary: interface for memcached server + * Description: main include file for libmemcached + * + */ +#ifndef LIBMEMCACHED_MEMCACHED_SASL_H +#define LIBMEMCACHED_MEMCACHED_SASL_H + +#ifdef LIBMEMCACHED_WITH_SASL_SUPPORT +#include + +#ifdef __cplusplus +extern "C" { +#endif + +LIBMEMCACHED_API +void memcached_set_sasl_callbacks(memcached_st *ptr, + const sasl_callback_t *callbacks); + +LIBMEMCACHED_API +memcached_return_t memcached_set_sasl_auth_data(memcached_st *ptr, + const char *username, + const char *password); + +LIBMEMCACHED_API +memcached_return_t memcached_destroy_sasl_auth_data(memcached_st *ptr); + + +LIBMEMCACHED_API +const sasl_callback_t *memcached_get_sasl_callbacks(memcached_st *ptr); + +LIBMEMCACHED_LOCAL +memcached_return_t memcached_clone_sasl(memcached_st *clone, const memcached_st *source); + +LIBMEMCACHED_LOCAL +memcached_return_t memcached_sasl_authenticate_connection(memcached_server_st *server); + +#ifdef __cplusplus +} +#endif + +#endif /* LIBMEMCACHED_WITH_SASL_SUPPORT */ + +#endif /* LIBMEMCACHED_MEMCACHED_SASL_H */ diff --git a/libmemcached/storage.c b/libmemcached/storage.c index 6225af37..abc5b7ca 100644 --- a/libmemcached/storage.c +++ b/libmemcached/storage.c @@ -488,12 +488,14 @@ static memcached_return_t memcached_send_binary(memcached_st *ptr, } /* write the header */ - if ((memcached_do(server, (const char*)request.bytes, send_length, false) != MEMCACHED_SUCCESS) || + memcached_return_t rc; + if (((rc= memcached_do(server, (const char*)request.bytes, + send_length, false)) != MEMCACHED_SUCCESS) || (memcached_io_write(server, key, key_length, false) == -1) || (memcached_io_write(server, value, value_length, flush) == -1)) { memcached_io_reset(server); - return MEMCACHED_WRITE_FAILURE; + return (rc == MEMCACHED_SUCCESS) ? MEMCACHED_WRITE_FAILURE : rc; } unlikely (verb == SET_OP && ptr->number_of_replicas > 0) diff --git a/libmemcached/strerror.c b/libmemcached/strerror.c index 6698ff18..4ed2a24c 100644 --- a/libmemcached/strerror.c +++ b/libmemcached/strerror.c @@ -84,6 +84,12 @@ const char *memcached_strerror(memcached_st *ptr __attribute__((unused)), memcac return "INVALID ARGUMENTS"; case MEMCACHED_KEY_TOO_BIG: return "KEY RETURNED FROM SERVER WAS TOO LARGE"; + case MEMCACHED_AUTH_PROBLEM: + return "FAILED TO SEND AUTHENTICATION TO SERVER"; + case MEMCACHED_AUTH_FAILURE: + return "AUTHENTICATION FAILURE"; + case MEMCACHED_AUTH_CONTINUE: + return "CONTINUE AUTHENTICATION"; case MEMCACHED_MAXIMUM_RETURN: return "Gibberish returned!"; default: diff --git a/m4/pandora_have_sasl.m4 b/m4/pandora_have_sasl.m4 new file mode 100644 index 00000000..75513d12 --- /dev/null +++ b/m4/pandora_have_sasl.m4 @@ -0,0 +1,133 @@ +dnl Copyright (C) 2009 Sun Microsystems +dnl This file is free software; Sun Microsystems +dnl gives unlimited permission to copy and/or distribute it, +dnl with or without modifications, as long as this notice is preserved. + +AC_DEFUN([_PANDORA_SEARCH_SASL],[ + AC_REQUIRE([AC_LIB_PREFIX]) + + dnl -------------------------------------------------------------------- + dnl Check for sasl + dnl -------------------------------------------------------------------- + AC_ARG_ENABLE([sasl], + [AS_HELP_STRING([--disable-sasl], + [Build with sasl support @<:@default=on@:>@])], + [ac_enable_sasl="$enableval"], + [ac_enable_sasl="yes"]) + + AS_IF([test "x$ac_enable_sasl" = "xyes"], + [ + AC_LIB_HAVE_LINKFLAGS(sasl,,[ + #include + #include + ],[ + sasl_server_init(NULL, NULL); + ]) + + AS_IF([test "x${ac_cv_libsasl}" != "xyes" ], + [ + AC_LIB_HAVE_LINKFLAGS(sasl2,,[ + #include + #include + ],[ + sasl_server_init(NULL, NULL); + ]) + HAVE_LIBSASL="$HAVE_LIBSASL2" + LIBSASL="$LIBSASL2" + LIBSASL_PREFIX="$LIBSASL2_PREFIX" + LTLIBSASL="$LT_LIBSASL2" + ]) + ]) + + AS_IF([test "x${ac_cv_libsasl}" = "xyes" -o "x${ac_cv_libsasl2}" = "xyes"], + [ac_cv_sasl=yes], + [ac_cv_sasl=no]) + + AM_CONDITIONAL(HAVE_LIBSASL, [test "x${ac_cv_libsasl}" = "xyes"]) + AM_CONDITIONAL(HAVE_LIBSASL2, [test "x${ac_cv_libsasl2}" = "xyes"]) + AM_CONDITIONAL(HAVE_SASL, [test "x${ac_cv_sasl}" = "xyes"]) +]) + +AC_DEFUN([PANDORA_HAVE_SASL],[ + AC_REQUIRE([_PANDORA_SEARCH_SASL]) +]) + +AC_DEFUN([PANDORA_REQUIRE_SASL],[ + AC_REQUIRE([_PANDORA_SEARCH_SASL]) + AS_IF([test "x${ac_cv_sasl}" = "xno"], + AC_MSG_ERROR([SASL (libsasl or libsasl2) is required for ${PACKAGE}])) +]) + +AC_DEFUN([_PANDORA_SEARCH_LIBSASL],[ + AC_REQUIRE([AC_LIB_PREFIX]) + + dnl -------------------------------------------------------------------- + dnl Check for libsasl + dnl -------------------------------------------------------------------- + + AC_ARG_ENABLE([libsasl], + [AS_HELP_STRING([--disable-libsasl], + [Build with libsasl support @<:@default=on@:>@])], + [ac_enable_libsasl="$enableval"], + [ac_enable_libsasl="yes"]) + + AS_IF([test "x$ac_enable_libsasl" = "xyes"],[ + AC_LIB_HAVE_LINKFLAGS(sasl,,[ + #include + #include + ],[ + sasl_server_init(NULL, NULL); + ]) + ],[ + ac_cv_libsasl="no" + ]) + + AM_CONDITIONAL(HAVE_LIBSASL, [test "x${ac_cv_libsasl}" = "xyes"]) +]) + +AC_DEFUN([PANDORA_HAVE_LIBSASL],[ + AC_REQUIRE([_PANDORA_SEARCH_LIBSASL]) +]) + +AC_DEFUN([PANDORA_REQUIRE_LIBSASL],[ + AC_REQUIRE([_PANDORA_SEARCH_LIBSASL]) + AS_IF([test "x${ac_cv_libsasl}" = "xno"], + AC_MSG_ERROR([libsasl is required for ${PACKAGE}])) +]) + +AC_DEFUN([_PANDORA_SEARCH_LIBSASL2],[ + AC_REQUIRE([AC_LIB_PREFIX]) + + dnl -------------------------------------------------------------------- + dnl Check for libsasl2 + dnl -------------------------------------------------------------------- + + AC_ARG_ENABLE([libsasl2], + [AS_HELP_STRING([--disable-libsasl2], + [Build with libsasl2 support @<:@default=on@:>@])], + [ac_enable_libsasl2="$enableval"], + [ac_enable_libsasl2="yes"]) + + AS_IF([test "x$ac_enable_libsasl2" = "xyes"],[ + AC_LIB_HAVE_LINKFLAGS(sasl2,,[ + #include + #include + ],[ + sasl2_server_init(NULL, NULL); + ]) + ],[ + ac_cv_libsasl2="no" + ]) + + AM_CONDITIONAL(HAVE_LIBSASL2, [test "x${ac_cv_libsasl2}" = "xyes"]) +]) + +AC_DEFUN([PANDORA_HAVE_LIBSASL2],[ + AC_REQUIRE([_PANDORA_SEARCH_LIBSASL2]) +]) + +AC_DEFUN([PANDORA_REQUIRE_LIBSASL2],[ + AC_REQUIRE([_PANDORA_SEARCH_LIBSASL2]) + AS_IF([test "x${ac_cv_libsasl2}" = "xno"], + AC_MSG_ERROR([libsasl2 is required for ${PACKAGE}])) +]) diff --git a/m4/protocol_binary.m4 b/m4/protocol_binary.m4 index d31bcb04..ba7acaf5 100644 --- a/m4/protocol_binary.m4 +++ b/m4/protocol_binary.m4 @@ -5,15 +5,16 @@ AC_DEFUN([PROTOCOL_BINARY_TEST], [AC_LANG_PUSH([C]) save_CFLAGS="$CFLAGS" CFLAGS="$CFLAGS -I${srcdir}" - AC_RUN_IFELSE([ + AC_RUN_IFELSE([ AC_LANG_PROGRAM([[ +#include #include "libmemcached/memcached/protocol_binary.h" ]],[[ protocol_binary_request_set request; if (sizeof(request) != sizeof(request.bytes)) { return 1; } - ]])],, AC_MSG_ERROR([Unsupported struct padding done by compiler.])) + ]])],, AC_MSG_ERROR([Unsupported struct padding done by compiler.])) CFLAGS="$save_CFLAGS" AC_LANG_POP ]) diff --git a/tests/include.am b/tests/include.am index b34f1f16..2577e2f4 100644 --- a/tests/include.am +++ b/tests/include.am @@ -52,50 +52,50 @@ tests_libtest_la_SOURCES= tests/test.c tests_testapp_CFLAGS= $(AM_CFLAGS) $(NO_CONVERSION) $(NO_STRICT_ALIASING) tests_testapp_SOURCES= tests/mem_functions.c -tests_testapp_LDADD= \ +tests_testapp_DEPENDENCIES= \ clients/libgenexec.la \ tests/libserver.la \ tests/libtest.la \ libmemcached/libmemcachedinternal.la \ -$(TESTS_LDADDS) -tests_testapp_DEPENDENCIES= $(tests_testapp_LDADD) + $(TESTS_LDADDS) +tests_testapp_LDADD= $(tests_testapp_DEPENDENCIES) $(LIBSASL) tests_testplus_SOURCES= tests/plus.cpp -tests_testplus_LDADD= tests/libtest.la tests/libserver.la $(TESTS_LDADDS) -tests_testplus_DEPENDENCIES= $(tests_testplus_LDADD) +tests_testplus_DEPENDENCIES= tests/libtest.la tests/libserver.la $(TESTS_LDADDS) +tests_testplus_LDADD= $(tests_testplus_DEPENDENCIES) $(LIBSASL) tests_atomsmasher_SOURCES= tests/atomsmasher.c -tests_atomsmasher_LDADD= \ +tests_atomsmasher_DEPENDENCIES= \ clients/libgenexec.la \ tests/libserver.la \ tests/libtest.la \ $(TESTS_LDADDS) -tests_atomsmasher_DEPENDENCIES= $(tests_atomsmasher_LDADD) +tests_atomsmasher_LDADD= $(tests_atomsmasher_DEPENDENCIES) $(LIBSASL) tests_testudp_CFLAGS= $(AM_CFLAGS) $(NO_CONVERSION) $(NO_STRICT_ALIASING) tests_testudp_SOURCES= tests/mem_udp.c -tests_testudp_LDADD= \ +tests_testudp_DEPENDENCIES= \ clients/libgenexec.la \ tests/libserver.la \ tests/libtest.la \ $(TESTS_LDADDS) -tests_testudp_DEPENDENCIES= $(tests_testudp_LDADD) +tests_testudp_LDADD= $(tests_testudp_DEPENDENCIES) $(LIBSASL) tests_startservers_SOURCES= tests/start.c -tests_startservers_LDADD= tests/libserver.la $(TESTS_LDADDS) -tests_startservers_DEPENDENCIES= $(tests_startservers_LDADD) +tests_startservers_DEPENDENCIES= tests/libserver.la $(TESTS_LDADDS) +tests_startservers_LDADD= $(tests_startservers_DEPENDENCIES) $(LIBSASL) tests_testhashkit_SOURCES = tests/hashkit_functions.c -tests_testhashkit_LDADD = tests/libtest.la libhashkit/libhashkit.la -tests_testhashkit_DEPENDENCIES = $(tests_testhashkit_LDADD) +tests_testhashkit_DEPENDENCIES = tests/libtest.la libhashkit/libhashkit.la +tests_testhashkit_LDADD = $(tests_testhashkit_DEPENDENCIES) $(LIBSASL) tests_hashplus_SOURCES = tests/hash_plus.cc -tests_hashplus_LDADD = $(tests_testhashkit_LDADD) -tests_hashplus_DEPENDENCIES = $(tests_testhashkit_LDADD) +tests_hashplus_DEPENDENCIES = $(tests_testhashkit_DEPENDENCIES) +tests_hashplus_LDADD = $(tests_testhashkit_DEPENDENCIES) $(LIBSASL) tests_memplus_SOURCES = tests/mem_plus.cc -tests_memplus_LDADD = tests/libtest.la tests/libserver.la libmemcached/libmemcached.la -tests_memplus_DEPENDENCIES = $(tests_memplus_LDADD) +tests_memplus_DEPENDENCIES = tests/libtest.la tests/libserver.la libmemcached/libmemcached.la +tests_memplus_LDADD = $(tests_memplus_DEPENDENCIES) test: test-docs test-mem test-hash memcapable echo "Tests completed" diff --git a/tests/libmemcached_world.h b/tests/libmemcached_world.h index b608d9ae..5a5e15b5 100644 --- a/tests/libmemcached_world.h +++ b/tests/libmemcached_world.h @@ -96,7 +96,7 @@ test_return_t world_flush(libmemcached_test_container_st *container) test_return_t world_pre_run(libmemcached_test_container_st *container) { - for (uint32_t loop= 0; loop < memcached_server_list_count(container->construct.servers); loop++) + for (uint32_t loop= 0; loop < memcached_server_list_count(container->memc->servers); loop++) { memcached_server_instance_st instance= memcached_server_instance_by_position(container->memc, loop); diff --git a/tests/mem_functions.c b/tests/mem_functions.c index 84ac0dc1..263119d6 100644 --- a/tests/mem_functions.c +++ b/tests/mem_functions.c @@ -376,10 +376,11 @@ static test_return_t error_test(memcached_st *memc) 4269430871U, 610793021U, 527273862U, 1437122909U, 2300930706U, 2943759320U, 674306647U, 2400528935U, 54481931U, 4186304426U, 1741088401U, 2979625118U, - 4159057246U, 3425930182U, 2593724503U, 1868899624U}; + 4159057246U, 3425930182U, 2593724503U, 1868899624U, + 1769812374U, 2302537950U, 1110330676U }; // You have updated the memcache_error messages but not updated docs/tests. - test_true(MEMCACHED_MAXIMUM_RETURN == 40); + test_true(MEMCACHED_MAXIMUM_RETURN == 43); for (rc= MEMCACHED_SUCCESS; rc < MEMCACHED_MAXIMUM_RETURN; rc++) { uint32_t hash_val; @@ -1215,6 +1216,10 @@ static test_return_t stats_servername_test(memcached_st *memc) memcached_server_instance_st instance= memcached_server_instance_by_position(memc, 0); +#ifdef LIBMEMCACHED_WITH_SASL_SUPPORT + if (memcached_get_sasl_callbacks(memc) != NULL) + return TEST_SKIPPED; +#endif rc= memcached_stat_servername(&memc_stat, NULL, memcached_server_name(instance), memcached_server_port(instance)); @@ -3467,7 +3472,7 @@ static test_return_t pre_cork(memcached_st *memc) static test_return_t pre_cork_and_nonblock(memcached_st *memc) { test_return_t rc; - + rc= pre_cork(memc); #ifdef __APPLE__ @@ -3642,6 +3647,31 @@ static test_return_t pre_binary(memcached_st *memc) return rc == MEMCACHED_SUCCESS ? TEST_SUCCESS : TEST_SKIPPED; } +static test_return_t pre_sasl(memcached_st *memc) +{ + memcached_return_t rc= MEMCACHED_FAILURE; + +#ifdef LIBMEMCACHED_WITH_SASL_SUPPORT + const char *server= getenv("LIBMEMCACHED_TEST_SASL_SERVER"); + const char *user= getenv("LIBMEMCACHED_TEST_SASL_USERNAME"); + const char *pass= getenv("LIBMEMCACHED_TEST_SASL_PASSWORD"); + + if (server != NULL && user != NULL && pass != NULL) + { + memcached_server_st *servers= memcached_servers_parse(server); + test_true(servers != NULL); + memcached_servers_reset(memc); + test_true(memcached_server_push(memc, servers) == MEMCACHED_SUCCESS); + memcached_behavior_set(memc, MEMCACHED_BEHAVIOR_BINARY_PROTOCOL, 1); + rc= memcached_set_sasl_auth_data(memc, user, pass); + test_true(rc == MEMCACHED_SUCCESS); + } +#else + (void)memc; +#endif + + return rc == MEMCACHED_SUCCESS ? TEST_SUCCESS : TEST_SKIPPED; +} static test_return_t pre_replication(memcached_st *memc) { @@ -4252,7 +4282,7 @@ static test_return_t connection_pool_test(memcached_st *memc) rc= memcached_set(mmc[0], key, keylen, "0", 1, 0, 0); test_true(rc == MEMCACHED_SUCCESS); - for (size_t x= 0; x < 10; ++x) + for (size_t x= 0; x < 10; ++x) { uint64_t number_value; rc= memcached_increment(mmc[x], key, keylen, 1, &number_value); @@ -4788,7 +4818,7 @@ static test_return_t memcached_get_hashkit_test (memcached_st *memc) test_true(md5_values[x] == hash_val); } - + /* Now check memcached_st. */ @@ -5499,8 +5529,36 @@ static test_return_t regression_bug_490486(memcached_st *memc) return TEST_SUCCESS; } +/* + * Test that the sasl authentication works. We cannot use the default + * pool of servers, because that would require that all servers we want + * to test supports SASL authentication, and that they use the default + * creds. + */ +static test_return_t sasl_auth_test(memcached_st *memc) +{ + memcached_return_t rc; + rc= memcached_set(memc, "foo", 3, "bar", 3, (time_t)0, (uint32_t)0); + test_true(rc == MEMCACHED_SUCCESS); + test_true((rc= memcached_delete(memc, "foo", 3, 0)) == MEMCACHED_SUCCESS); + test_true((rc= memcached_destroy_sasl_auth_data(memc)) == MEMCACHED_SUCCESS); + test_true((rc= memcached_destroy_sasl_auth_data(memc)) == MEMCACHED_FAILURE); + test_true((rc= memcached_destroy_sasl_auth_data(NULL)) == MEMCACHED_FAILURE); + memcached_quit(memc); + + rc= memcached_set_sasl_auth_data(memc, + getenv("LIBMEMCACHED_TEST_SASL_USERNAME"), + getenv("LIBMEMCACHED_TEST_SASL_SERVER")); + test_true(rc == MEMCACHED_SUCCESS); + rc= memcached_set(memc, "foo", 3, "bar", 3, (time_t)0, (uint32_t)0); + test_true(rc == MEMCACHED_AUTH_FAILURE); + test_true(memcached_destroy_sasl_auth_data(memc) == MEMCACHED_SUCCESS); + + memcached_quit(memc); + return TEST_SUCCESS; +} /* Clean the server before beginning testing */ test_st tests[] ={ @@ -5661,6 +5719,13 @@ test_st regression_tests[]= { {0, 0, (test_callback_fn)0} }; +#ifdef LIBMEMCACHED_WITH_SASL_SUPPORT +test_st sasl_auth_tests[]= { + {"sasl_auth", 1, (test_callback_fn)sasl_auth_test }, + {0, 0, (test_callback_fn)0} +}; +#endif + test_st ketama_compatibility[]= { {"libmemcached", 1, (test_callback_fn)ketama_compatibility_libmemcached }, {"spymemcached", 1, (test_callback_fn)ketama_compatibility_spymemcached }, @@ -5766,6 +5831,8 @@ collection_st collection[] ={ #endif {"memory_allocators", (test_callback_fn)set_memory_alloc, 0, tests}, {"prefix", (test_callback_fn)set_prefix, 0, tests}, + {"sasl_auth", (test_callback_fn)pre_sasl, 0, sasl_auth_tests }, + {"sasl", (test_callback_fn)pre_sasl, 0, tests }, {"version_1_2_3", (test_callback_fn)check_for_1_2_3, 0, version_1_2_3}, {"string", 0, 0, string_tests}, {"result", 0, 0, result_tests}, diff --git a/tests/server.c b/tests/server.c index 383469bd..c96f5972 100644 --- a/tests/server.c +++ b/tests/server.c @@ -13,7 +13,7 @@ Startup, and shutdown the memcached servers. */ -#define TEST_PORT_BASE MEMCACHED_DEFAULT_PORT+10 +#define TEST_PORT_BASE MEMCACHED_DEFAULT_PORT+10 #include "config.h" @@ -52,7 +52,7 @@ void server_startup(server_startup_st *construct) int status; sprintf(buffer, "/tmp/%umemc.pid", x); - if (access(buffer, F_OK) == 0) + if (access(buffer, F_OK) == 0) { FILE *fp= fopen(buffer, "r"); remove(buffer); @@ -60,9 +60,9 @@ void server_startup(server_startup_st *construct) if (fp != NULL) { if (fgets(buffer, sizeof(buffer), fp) != NULL) - { + { pid_t pid= (pid_t)atoi(buffer); - if (pid != 0) + if (pid != 0) kill(pid, SIGTERM); } @@ -74,7 +74,7 @@ void server_startup(server_startup_st *construct) { sprintf(buffer, "%s -d -P /tmp/%umemc.pid -t 1 -p %u -U %u -m 128", MEMCACHED_BINARY, x, x + TEST_PORT_BASE, x + TEST_PORT_BASE); - } + } else { sprintf(buffer, "%s -d -P /tmp/%umemc.pid -t 1 -p %u -U %u", @@ -119,7 +119,7 @@ void server_shutdown(server_startup_st *construct) sprintf(buffer, "cat /tmp/%umemc.pid | xargs kill", x); /* We have to check the return value of this or the compiler will yell */ int sys_ret= system(buffer); - assert(sys_ret != -1); + assert(sys_ret != -1); sprintf(buffer, "/tmp/%umemc.pid", x); unlink(buffer); } diff --git a/tests/test.c b/tests/test.c index b177f64f..dd64be69 100644 --- a/tests/test.c +++ b/tests/test.c @@ -21,6 +21,7 @@ #include #include +#include "libmemcached/memcached.h" #include "test.h" static void world_stats_print(world_stats_st *stats) @@ -147,6 +148,14 @@ int main(int argc, char *argv[]) world_stats_st stats; +#ifdef LIBMEMCACHED_WITH_SASL_SUPPORT + if (sasl_client_init(NULL) != SASL_OK) + { + fprintf(stderr, "Failed to initialize sasl library!\n"); + return 1; + } +#endif + memset(&stats, 0, sizeof(stats)); memset(&world, 0, sizeof(world)); get_world(&world); @@ -350,5 +359,9 @@ cleanup: world_stats_print(&stats); +#ifdef LIBMEMCACHED_WITH_SASL_SUPPORT + sasl_done(); +#endif + return stats.failed == 0 ? 0 : 1; } -- 2.30.2