ext/libcouchbase/src/mcserver/negotiate.cc in libcouchbase-0.0.9 vs ext/libcouchbase/src/mcserver/negotiate.cc in libcouchbase-0.1.0

- old
+ new

@@ -14,28 +14,34 @@ * See the License for the specific language governing permissions and * limitations under the License. */ #include <algorithm> +#include <string> +#include <sstream> +#include <vector> + #include "packetutils.h" #include "mcserver.h" #include "logging.h" #include "settings.h" #include <lcbio/lcbio.h> #include <lcbio/timer-ng.h> #include <lcbio/ssl.h> #include <cbsasl/cbsasl.h> #include "negotiate.h" #include "ctx-log-inl.h" +#include "auth-priv.h" using namespace lcb; #define LOGARGS(ctx, lvl) ctx->settings, "negotiation", LCB_LOG_##lvl, __FILE__, __LINE__ static void cleanup_negotiated(SessionInfo* info); static void handle_ioerr(lcbio_CTX *ctx, lcb_error_t err); #define SESSREQ_LOGFMT "<%s:%s> (SASLREQ=%p) " + static void timeout_handler(void *arg); #define SESSREQ_LOGID(s) get_ctx_host(s->ctx), get_ctx_port(s->ctx), (void*)s static void @@ -63,13 +69,16 @@ bool send_hello(); bool send_step(const lcb::MemcachedResponse& packet); bool read_hello(const lcb::MemcachedResponse& packet); void send_auth(const char *sasl_data, unsigned ndata); void handle_read(lcbio_CTX *ioctx); + bool maybe_select_bucket(); enum MechStatus { MECH_UNAVAILABLE, MECH_NOT_NEEDED, MECH_OK }; MechStatus set_chosen_mech(std::string& mechlist, const char **data, unsigned int *ndata); + bool request_errmap(); + bool update_errmap(const lcb::MemcachedResponse& packet); SessionRequestImpl(lcbio_CONNDONE_cb callback, void *data, uint32_t timeout, lcbio_TABLE *iot, lcb_settings* settings_) : ctx(NULL), cb(callback), cbdata(data), timer(lcbio_timer_new(iot, this, timeout_handler)), last_err(LCB_SUCCESS), info(NULL), @@ -322,10 +331,14 @@ lcb_U16 features[MEMCACHED_TOTAL_HELLO_FEATURES]; unsigned nfeatures = 0; features[nfeatures++] = PROTOCOL_BINARY_FEATURE_TLS; features[nfeatures++] = PROTOCOL_BINARY_FEATURE_XATTR; + features[nfeatures++] = PROTOCOL_BINARY_FEATURE_SELECT_BUCKET; + if (settings->use_errmap) { + features[nfeatures++] = PROTOCOL_BINARY_FEATURE_XERROR; + } if (settings->tcp_nodelay) { features[nfeatures++] = PROTOCOL_BINARY_FEATURE_TCPNODELAY; } #ifndef LCB_NO_SNAPPY @@ -358,10 +371,11 @@ lcbio_ctx_put(ctx, clistr, nclistr); for (size_t ii = 0; ii < nfeatures; ii++) { lcb_U16 tmp = htons(features[ii]); lcbio_ctx_put(ctx, &tmp, sizeof tmp); } + lcbio_ctx_rwant(ctx, 24); return true; } bool @@ -379,27 +393,83 @@ info->server_features.push_back(tmp); } return true; } -typedef enum { - SREQ_S_WAIT, - SREQ_S_AUTHDONE, - SREQ_S_HELLODONE, - SREQ_S_ERROR -} sreq_STATE; +bool +SessionRequestImpl::request_errmap() { + lcb::MemcachedRequest hdr(PROTOCOL_BINARY_CMD_GET_ERROR_MAP); + uint16_t version = htons(1); + hdr.sizes(0, 0, 2); + const char *p = reinterpret_cast<const char *>(&version); + lcbio_ctx_put(ctx, hdr.data(), hdr.size()); + lcbio_ctx_put(ctx, p, 2); + lcbio_ctx_rwant(ctx, 24); + return true; +} + +bool +SessionRequestImpl::update_errmap(const lcb::MemcachedResponse& resp) +{ + // Get the error map object + using lcb::errmap::ErrorMap; + + std::string errmsg; + ErrorMap& mm = *settings->errmap; + ErrorMap::ParseStatus status = mm.parse( + resp.body<const char*>(), resp.bodylen(), errmsg); + + if (status != ErrorMap::UPDATED && status != ErrorMap::NOT_UPDATED) { + errmsg = "Couldn't update error map: " + errmsg; + set_error(LCB_PROTOCOL_ERROR, errmsg.c_str()); + return false; + } + + return true; +} + +// Returns true if sending the SELECT_BUCKET command, false otherwise. +bool +SessionRequestImpl::maybe_select_bucket() { + + // Only send a SELECT_BUCKET if we have the SELECT_BUCKET bit enabled. + if (!info->has_feature(PROTOCOL_BINARY_FEATURE_SELECT_BUCKET)) { + return false; + } + + if (!settings->select_bucket) { + lcb_log(LOGARGS(this, WARN), SESSREQ_LOGFMT "SELECT_BUCKET Disabled by application", SESSREQ_LOGID(this)); + return false; + } + + // send the SELECT_BUCKET command: + lcb_log(LOGARGS(this, INFO), SESSREQ_LOGFMT "Sending SELECT_BUCKET", SESSREQ_LOGID(this)); + lcb::MemcachedRequest req(PROTOCOL_BINARY_CMD_SELECT_BUCKET); + req.sizes(0, strlen(settings->bucket), 0); + lcbio_ctx_put(ctx, req.data(), req.size()); + lcbio_ctx_put(ctx, settings->bucket, strlen(settings->bucket)); + LCBIO_CTX_RSCHEDULE(ctx, 24); + return true; +} + +static bool isUnsupported(uint16_t status) { + return status == PROTOCOL_BINARY_RESPONSE_NOT_SUPPORTED || + status == PROTOCOL_BINARY_RESPONSE_UNKNOWN_COMMAND || + status == PROTOCOL_BINARY_RESPONSE_EACCESS; +} + /** * It's assumed the server buffers will be reset upon close(), so we must make * sure to _not_ release the ringbuffer if that happens. */ void SessionRequestImpl::handle_read(lcbio_CTX *ioctx) { lcb::MemcachedResponse resp; unsigned required; - sreq_STATE state = SREQ_S_WAIT; + bool completed = false; GT_NEXT_PACKET: if (!resp.load(ioctx, &required)) { LCBIO_CTX_RSCHEDULE(ioctx, required); @@ -414,70 +484,96 @@ std::string mechs(resp.body<const char*>(), resp.bodylen()); MechStatus mechrc = set_chosen_mech(mechs, &mechlist_data, &nmechlist_data); if (mechrc == MECH_OK) { send_auth(mechlist_data, nmechlist_data); - state = SREQ_S_WAIT; } else if (mechrc == MECH_UNAVAILABLE) { - state = SREQ_S_ERROR; + // Do nothing - error already set } else { - state = SREQ_S_HELLODONE; + completed = !maybe_select_bucket(); } break; } case PROTOCOL_BINARY_CMD_SASL_AUTH: { if (status == PROTOCOL_BINARY_RESPONSE_SUCCESS) { - send_hello(); - state = SREQ_S_AUTHDONE; + completed = !maybe_select_bucket(); break; - } - - if (status != PROTOCOL_BINARY_RESPONSE_AUTH_CONTINUE) { + } else if (status == PROTOCOL_BINARY_RESPONSE_AUTH_CONTINUE) { + send_step(resp); + } else { set_error(LCB_AUTH_ERROR, "SASL AUTH failed"); - state = SREQ_S_ERROR; break; } - if (send_step(resp) && send_hello()) { - state = SREQ_S_WAIT; - } else { - state = SREQ_S_ERROR; - } break; } case PROTOCOL_BINARY_CMD_SASL_STEP: { - if (status != PROTOCOL_BINARY_RESPONSE_SUCCESS) { + if (status == PROTOCOL_BINARY_RESPONSE_SUCCESS) { + completed = !maybe_select_bucket(); + } else { lcb_log(LOGARGS(this, WARN), SESSREQ_LOGFMT "SASL auth failed with STATUS=0x%x", SESSREQ_LOGID(this), status); set_error(LCB_AUTH_ERROR, "SASL Step Failed"); - state = SREQ_S_ERROR; - } else { - /* Wait for pipelined HELLO response */ - state = SREQ_S_AUTHDONE; } break; } case PROTOCOL_BINARY_CMD_HELLO: { - state = SREQ_S_HELLODONE; if (status == PROTOCOL_BINARY_RESPONSE_SUCCESS) { if (!read_hello(resp)) { set_error(LCB_PROTOCOL_ERROR, "Couldn't parse HELLO"); + break; } - } else if (status == PROTOCOL_BINARY_RESPONSE_UNKNOWN_COMMAND || - status == PROTOCOL_BINARY_RESPONSE_NOT_SUPPORTED) { + } else if (isUnsupported(status)) { lcb_log(LOGARGS(this, DEBUG), SESSREQ_LOGFMT "Server does not support HELLO", SESSREQ_LOGID(this)); - /* nothing */ } else { set_error(LCB_PROTOCOL_ERROR, "Hello response unexpected"); - state = SREQ_S_ERROR; + break; } + + if (info->has_feature(PROTOCOL_BINARY_FEATURE_XERROR)) { + request_errmap(); + } else { + lcb_log(LOGARGS(this, TRACE), SESSREQ_LOGFMT "GET_ERRORMAP unsupported/disabled", SESSREQ_LOGID(this)); + } + + // In any event, it's also time to send the LIST_MECHS request + lcb::MemcachedRequest req(PROTOCOL_BINARY_CMD_SASL_LIST_MECHS); + lcbio_ctx_put(ctx, req.data(), req.size()); + LCBIO_CTX_RSCHEDULE(ctx, 24); + break; } + case PROTOCOL_BINARY_CMD_GET_ERROR_MAP: { + if (status == PROTOCOL_BINARY_RESPONSE_SUCCESS) { + if (!update_errmap(resp)) { + } + } else if (isUnsupported(status)) { + lcb_log(LOGARGS(this, DEBUG), SESSREQ_LOGFMT "Server does not support GET_ERRMAP (0x%x)", SESSREQ_LOGID(this), status); + } else { + lcb_log(LOGARGS(this, ERROR), SESSREQ_LOGFMT "Unexpected status 0x%x received for GET_ERRMAP", SESSREQ_LOGID(this), status); + set_error(LCB_PROTOCOL_ERROR, "GET_ERRMAP response unexpected"); + } + // Note, there is no explicit state transition here. LIST_MECHS is + // pipelined after this request. + break; + } + + case PROTOCOL_BINARY_CMD_SELECT_BUCKET: { + if (status == PROTOCOL_BINARY_RESPONSE_SUCCESS) { + completed = true; + } else if (status == PROTOCOL_BINARY_RESPONSE_EACCESS) { + set_error(LCB_AUTH_ERROR, "Provided credentials not allowed for bucket"); + } else { + lcb_log(LOGARGS(this, ERROR), SESSREQ_LOGFMT "Unexpected status 0x%x received for SELECT_BUCKET", SESSREQ_LOGID(this), status); + set_error(LCB_PROTOCOL_ERROR, "Other auth error"); + } + break; + } + default: { - state = SREQ_S_ERROR; lcb_log(LOGARGS(this, ERROR), SESSREQ_LOGFMT "Received unknown response. OP=0x%x. RC=0x%x", SESSREQ_LOGID(this), resp.opcode(), resp.status()); set_error(LCB_NOT_SUPPORTED, "Received unknown response"); break; } } @@ -488,13 +584,11 @@ // Once there is no more any dependencies on the buffers, we can succeed // or fail the request, potentially destroying the underlying connection if (has_error()) { fail(); - } else if (state == SREQ_S_ERROR) { - fail(LCB_ERROR, "FIXME: Error code set without description"); - } else if (state == SREQ_S_HELLODONE) { + } else if (completed) { success(); } else { goto GT_NEXT_PACKET; } } @@ -535,11 +629,10 @@ set_error(LCB_EINTERNAL, "Couldn't start SASL client"); lcbio_async_signal(timer); return; } - lcb::MemcachedRequest hdr(PROTOCOL_BINARY_CMD_SASL_LIST_MECHS); - lcbio_ctx_put(ctx, hdr.data(), hdr.size()); + send_hello(); LCBIO_CTX_RSCHEDULE(ctx, 24); } SessionRequestImpl::~SessionRequestImpl() {