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()
{