From f1307c4d9cadb94076a99cc2f88a00f7e0b4161f Mon Sep 17 00:00:00 2001 From: Dustin Sallings Date: Sun, 10 May 2009 00:50:47 -0700 Subject: [PATCH] SASL auth support. --- Makefile.am | 4 + memcached.c | 229 ++++++++++++++++++++++++++++++++++++++++++++++ memcached.h | 9 +- protocol_binary.h | 6 ++ sasl_defs.c | 20 ++++ sasl_defs.h | 26 ++++++ testapp.c | 6 +- 7 files changed, 298 insertions(+), 2 deletions(-) create mode 100644 sasl_defs.c create mode 100644 sasl_defs.h diff --git a/Makefile.am b/Makefile.am index 5989414..de8213d 100644 --- a/Makefile.am +++ b/Makefile.am @@ -27,6 +27,10 @@ if BUILD_SOLARIS_PRIVS memcached_SOURCES += solaris_priv.c endif +if ENABLE_SASL +memcached_SOURCES += sasl_defs.c +endif + memcached_debug_SOURCES = $(memcached_SOURCES) memcached_CPPFLAGS = -DNDEBUG memcached_debug_LDADD = @PROFILER_LDFLAGS@ diff --git a/memcached.c b/memcached.c index da622f0..3732dbc 100644 --- a/memcached.c +++ b/memcached.c @@ -94,6 +94,7 @@ static int ensure_iov_space(conn *c); static int add_iov(conn *c, const void *buf, int len); static int add_msghdr(conn *c); + /* time handling */ static void set_current_time(void); /* update the global variable holding global 32-bit seconds-since-start time @@ -463,6 +464,12 @@ static void conn_cleanup(conn *c) { free(c->write_and_free); c->write_and_free = 0; } + + if (c->sasl_conn) { + assert(settings.sasl); + sasl_dispose(&c->sasl_conn); + c->sasl_conn = NULL; + } } /* @@ -937,6 +944,9 @@ static void write_bin_error(conn *c, protocol_binary_response_status err, int sw case PROTOCOL_BINARY_RESPONSE_NOT_STORED: errstr = "Not stored."; break; + case PROTOCOL_BINARY_RESPONSE_AUTH_ERROR: + errstr = "Auth failure."; + break; default: assert(false); errstr = "UNHANDLED ERROR"; @@ -1454,6 +1464,182 @@ static void handle_binary_protocol_error(conn *c) { c->write_and_go = conn_closing; } +static void init_sasl_conn(conn *c) { + assert(c); + /* should something else be returned? */ + if (!settings.sasl) + return; + + if (!c->sasl_conn) { + int result=sasl_server_new("memcached", + NULL, NULL, NULL, NULL, + NULL, 0, &c->sasl_conn); + if (result != SASL_OK) { + if (settings.verbose) { + fprintf(stderr, "Failed to initialize SASL conn.\n"); + } + c->sasl_conn = NULL; + } + } +} + +static void bin_list_sasl_mechs(conn *c) { + // Guard against a disabled SASL. + if (!settings.sasl) { + write_bin_error(c, PROTOCOL_BINARY_RESPONSE_UNKNOWN_COMMAND, + c->binary_header.request.bodylen + - c->binary_header.request.keylen); + return; + } + + init_sasl_conn(c); + const char *result_string = NULL; + unsigned int string_length = 0; + int result=sasl_listmech(c->sasl_conn, NULL, + "", /* What to prepend the string with */ + " ", /* What to separate mechanisms with */ + "", /* What to append to the string */ + &result_string, &string_length, + NULL); + if (result != SASL_OK) { + /* Perhaps there's a better error for this... */ + if (settings.verbose) { + fprintf(stderr, "Failed to list SASL mechanisms.\n"); + } + write_bin_error(c, PROTOCOL_BINARY_RESPONSE_AUTH_ERROR, 0); + return; + } + write_bin_response(c, (char*)result_string, 0, 0, string_length); +} + +static void process_bin_sasl_auth(conn *c) { + // Guard for handling disabled SASL on the server. + if (!settings.sasl) { + write_bin_error(c, PROTOCOL_BINARY_RESPONSE_UNKNOWN_COMMAND, + c->binary_header.request.bodylen + - c->binary_header.request.keylen); + return; + } + + assert(c->binary_header.request.extlen == 0); + + int nkey = c->binary_header.request.keylen; + int vlen = c->binary_header.request.bodylen - nkey; + + char *key = binary_get_key(c); + assert(key); + + item *it = item_alloc(key, nkey, 0, 0, vlen); + + if (it == 0) { + write_bin_error(c, PROTOCOL_BINARY_RESPONSE_ENOMEM, vlen); + c->write_and_go = conn_swallow; + return; + } + + c->item = it; + c->ritem = ITEM_data(it); + c->rlbytes = vlen; + conn_set_state(c, conn_nread); + c->substate = bin_reading_sasl_auth_data; +} + +static void process_bin_complete_sasl_auth(conn *c) { + assert(settings.sasl); + const char *out = NULL; + unsigned int outlen = 0; + + assert(c->item); + init_sasl_conn(c); + + int nkey = c->binary_header.request.keylen; + int vlen = c->binary_header.request.bodylen - nkey; + + char mech[nkey+1]; + memcpy(mech, ITEM_key((item*)c->item), nkey); + mech[nkey] = 0x00; + + if (settings.verbose) + fprintf(stderr, "mech: ``%s'' with %d bytes of data\n", mech, vlen); + + const char *challenge = vlen == 0 ? NULL : ITEM_data((item*) c->item); + + int result=-1; + + switch (c->cmd) { + case PROTOCOL_BINARY_CMD_SASL_AUTH: + result = sasl_server_start(c->sasl_conn, mech, + challenge, vlen, + &out, &outlen); + break; + case PROTOCOL_BINARY_CMD_SASL_STEP: + result = sasl_server_step(c->sasl_conn, + challenge, vlen, + &out, &outlen); + break; + default: + assert(false); /* CMD should be one of the above */ + /* This code is pretty much impossible, but makes the compiler + happier */ + if (settings.verbose) { + fprintf(stderr, "Unhandled command %d with challenge %s\n", + c->cmd, challenge); + } + break; + } + + item_unlink(c->item); + + if (settings.verbose) { + fprintf(stderr, "sasl result code: %d\n", result); + } + + switch(result) { + case SASL_OK: + write_bin_response(c, "Authenticated", 0, 0, strlen("Authenticated")); + break; + case SASL_CONTINUE: + add_bin_header(c, PROTOCOL_BINARY_RESPONSE_AUTH_CONTINUE, 0, 0, outlen); + if(outlen > 0) { + add_iov(c, out, outlen); + } + conn_set_state(c, conn_mwrite); + c->write_and_go = conn_new_cmd; + break; + default: + if (settings.verbose) + fprintf(stderr, "Unknown sasl response: %d\n", result); + write_bin_error(c, PROTOCOL_BINARY_RESPONSE_AUTH_ERROR, 0); + } +} + +static bool authenticated(conn *c) { + assert(settings.sasl); + bool rv = false; + + switch (c->cmd) { + case PROTOCOL_BINARY_CMD_SASL_LIST_MECHS: /* FALLTHROUGH */ + case PROTOCOL_BINARY_CMD_SASL_AUTH: /* FALLTHROUGH */ + case PROTOCOL_BINARY_CMD_SASL_STEP: /* FALLTHROUGH */ + case PROTOCOL_BINARY_CMD_VERSION: /* FALLTHROUGH */ + rv = true; + break; + default: + if (c->sasl_conn) { + const void *uname = NULL; + sasl_getprop(c->sasl_conn, SASL_USERNAME, &uname); + rv = uname != NULL; + } + } + + if (settings.verbose > 1) { + fprintf(stderr, "authenticated() in cmd 0x%02x is %s\n", + c->cmd, rv ? "true" : "false"); + } + + return rv; +} + static void dispatch_bin_command(conn *c) { int protocol_error = 0; @@ -1461,6 +1647,12 @@ static void dispatch_bin_command(conn *c) { int keylen = c->binary_header.request.keylen; uint32_t bodylen = c->binary_header.request.bodylen; + if (settings.sasl && !authenticated(c)) { + write_bin_error(c, PROTOCOL_BINARY_RESPONSE_AUTH_ERROR, 0); + c->write_and_go = conn_closing; + return; + } + MEMCACHED_PROCESS_COMMAND_START(c->sfd, c->rcurr, c->rbytes); c->noreply = true; @@ -1593,6 +1785,21 @@ static void dispatch_bin_command(conn *c) { protocol_error = 1; } break; + case PROTOCOL_BINARY_CMD_SASL_LIST_MECHS: + if (extlen == 0 && keylen == 0 && bodylen == 0) { + bin_list_sasl_mechs(c); + } else { + protocol_error = 1; + } + break; + case PROTOCOL_BINARY_CMD_SASL_AUTH: + case PROTOCOL_BINARY_CMD_SASL_STEP: + if (extlen == 0 && keylen != 0) { + bin_read_key(c, bin_reading_sasl_auth, 0); + } else { + protocol_error = 1; + } + break; default: write_bin_error(c, PROTOCOL_BINARY_RESPONSE_UNKNOWN_COMMAND, bodylen); } @@ -1834,6 +2041,12 @@ static void complete_nread_binary(conn *c) { case bin_read_flush_exptime: process_bin_flush(c); break; + case bin_reading_sasl_auth: + process_bin_sasl_auth(c); + break; + case bin_reading_sasl_auth_data: + process_bin_complete_sasl_auth(c); + break; default: fprintf(stderr, "Not handling substate %d\n", c->substate); assert(0); @@ -3814,6 +4027,9 @@ static void usage(void) { printf("-B Binding protocol - one of ascii, binary, or auto (default)\n"); printf("-I Override the size of each slab page. Adjusts max item size\n" " (default: 1mb, min: 1k, max: 128m)\n"); +#ifdef ENABLE_SASL + printf("-S Turn on Sasl authentication\n"); +#endif return; } @@ -4029,6 +4245,7 @@ int main (int argc, char **argv) { "b:" /* backlog queue limit */ "B:" /* Binding protocol */ "I:" /* Max item size */ + "S" /* Sasl ON */ ))) { switch (c) { case 'a': @@ -4181,6 +4398,13 @@ int main (int argc, char **argv) { ); } break; + case 'S': /* set Sasl authentication to true. Default is false */ +#ifndef ENABLE_SASL + fprintf(stderr, "This server is not built with SASL support.\n"); + exit(EX_USAGE); +#endif + settings.sasl = true; + break; default: fprintf(stderr, "Illegal argument \"%c\"\n", c); return 1; @@ -4249,6 +4473,11 @@ int main (int argc, char **argv) { } } + /* Initialize Sasl if -S was specified */ + if (settings.sasl) { + init_sasl(); + } + /* daemonize if requested */ /* if we want to ensure our ability to dump core, don't chdir to / */ if (do_daemonize) { diff --git a/memcached.h b/memcached.h index a52e0bd..9b49012 100644 --- a/memcached.h +++ b/memcached.h @@ -18,10 +18,13 @@ #include #include #include +#include #include "protocol_binary.h" #include "cache.h" +#include "sasl_defs.h" + /** Maximum length of a key. */ #define KEY_MAX_LENGTH 250 @@ -155,7 +158,9 @@ enum bin_substates { bin_reading_stat, bin_reading_del_header, bin_reading_incr_header, - bin_read_flush_exptime + bin_read_flush_exptime, + bin_reading_sasl_auth, + bin_reading_sasl_auth_data }; enum protocol { @@ -268,6 +273,7 @@ struct settings { enum protocol binding_protocol; int backlog; int item_size_max; /* Maximum item size, and upper end for slabs */ + bool sasl; /* SASL on/off */ }; extern struct stats stats; @@ -324,6 +330,7 @@ typedef struct { typedef struct conn conn; struct conn { int sfd; + sasl_conn_t *sasl_conn; enum conn_states state; enum bin_substates substate; struct event event; diff --git a/protocol_binary.h b/protocol_binary.h index cb81331..e132f00 100644 --- a/protocol_binary.h +++ b/protocol_binary.h @@ -69,6 +69,8 @@ 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_status; @@ -106,6 +108,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. diff --git a/sasl_defs.c b/sasl_defs.c new file mode 100644 index 0000000..7e803d4 --- /dev/null +++ b/sasl_defs.c @@ -0,0 +1,20 @@ +#include "memcached.h" +#include +#include + +static sasl_callback_t sasl_callbacks[] = { + { + SASL_CB_LIST_END, NULL, NULL + } +}; + +void init_sasl(void) { + if (sasl_server_init(sasl_callbacks, "memcached") != SASL_OK) { + fprintf(stderr, "Error initializing sasl.\n"); + exit(EXIT_FAILURE); + } else { + if (settings.verbose) { + fprintf(stderr, "Initialized SASL.\n"); + } + } +} diff --git a/sasl_defs.h b/sasl_defs.h new file mode 100644 index 0000000..39c3cbf --- /dev/null +++ b/sasl_defs.h @@ -0,0 +1,26 @@ +#ifndef SASL_DEFS_H +#define SASL_DEFS_H 1 + +#if defined(HAVE_SASL_SASL_H) && defined(ENABLE_SASL) + +#include +void init_sasl(void); + +#else /* End of SASL support */ + +typedef void* sasl_conn_t; + +#define init_sasl() {} +#define sasl_dispose(x) {} +#define sasl_server_new(a, b, c, d, e, f, g, h) 1 +#define sasl_listmech(a, b, c, d, e, f, g, h) 1 +#define sasl_server_start(a, b, c, d, e, f) 1 +#define sasl_server_step(a, b, c, d, e) 1 +#define sasl_getprop(a, b, c) {} + +#define SASL_OK 0 +#define SASL_CONTINUE -1 + +#endif /* sasl compat */ + +#endif /* SASL_DEFS_H */ diff --git a/testapp.c b/testapp.c index 4a9dd10..a1bf42e 100644 --- a/testapp.c +++ b/testapp.c @@ -1475,7 +1475,7 @@ static enum test_return test_binary_stat(void) { } static enum test_return test_binary_illegal(void) { - uint8_t cmd = 0x1b; + uint8_t cmd = 0x23; while (cmd != 0x00) { union { protocol_binary_request_no_extras request; @@ -1585,6 +1585,10 @@ static enum test_return test_binary_pipeline_hickup_chunk(void *buffer, size_t b NULL, 0, NULL, 0); break; + case PROTOCOL_BINARY_CMD_SASL_LIST_MECHS: + case PROTOCOL_BINARY_CMD_SASL_AUTH: + case PROTOCOL_BINARY_CMD_SASL_STEP: + /* Ignoring SASL */ case PROTOCOL_BINARY_CMD_QUITQ: case PROTOCOL_BINARY_CMD_QUIT: /* I don't want to pass on the quit commands ;-) */