diff options
Diffstat (limited to 'trunk/qpid/cpp/src/qpid/broker/SaslAuthenticator.cpp')
-rw-r--r-- | trunk/qpid/cpp/src/qpid/broker/SaslAuthenticator.cpp | 269 |
1 files changed, 269 insertions, 0 deletions
diff --git a/trunk/qpid/cpp/src/qpid/broker/SaslAuthenticator.cpp b/trunk/qpid/cpp/src/qpid/broker/SaslAuthenticator.cpp new file mode 100644 index 0000000000..136cf6f785 --- /dev/null +++ b/trunk/qpid/cpp/src/qpid/broker/SaslAuthenticator.cpp @@ -0,0 +1,269 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ + +#include "config.h" + +#include "Connection.h" +#include "qpid/log/Statement.h" +#include "qpid/framing/reply_exceptions.h" + +#if HAVE_SASL +#include <sasl/sasl.h> +#endif + +using namespace qpid::framing; + +namespace qpid { +namespace broker { + + +class NullAuthenticator : public SaslAuthenticator +{ + Connection& connection; + framing::AMQP_ClientProxy::Connection client; +public: + NullAuthenticator(Connection& connection); + ~NullAuthenticator(); + void getMechanisms(framing::Array& mechanisms); + void start(const std::string& mechanism, const std::string& response); + void step(const std::string&) {} +}; + +#if HAVE_SASL + +class CyrusAuthenticator : public SaslAuthenticator +{ + sasl_conn_t *sasl_conn; + Connection& connection; + framing::AMQP_ClientProxy::Connection client; + + void processAuthenticationStep(int code, const char *challenge, unsigned int challenge_len); + +public: + CyrusAuthenticator(Connection& connection); + ~CyrusAuthenticator(); + void init(); + void getMechanisms(framing::Array& mechanisms); + void start(const std::string& mechanism, const std::string& response); + void step(const std::string& response); +}; + +#else + +typedef NullAuthenticator CyrusAuthenticator; + +#endif + +std::auto_ptr<SaslAuthenticator> SaslAuthenticator::createAuthenticator(Connection& c) +{ + if (c.getBroker().getOptions().auth) { + return std::auto_ptr<SaslAuthenticator>(new CyrusAuthenticator(c)); + } else { + return std::auto_ptr<SaslAuthenticator>(new NullAuthenticator(c)); + } +} + +NullAuthenticator::NullAuthenticator(Connection& c) : connection(c), client(c.getOutput()) {} +NullAuthenticator::~NullAuthenticator() {} + +void NullAuthenticator::getMechanisms(Array& mechanisms) +{ + mechanisms.add(boost::shared_ptr<FieldValue>(new Str16Value("ANONYMOUS"))); +} + +void NullAuthenticator::start(const string& mechanism, const string& response) +{ + QPID_LOG(warning, "SASL: No Authentication Performed"); + if (mechanism == "PLAIN") { // Old behavior + if (response.size() > 0 && response[0] == (char) 0) { + string temp = response.substr(1); + string::size_type i = temp.find((char)0); + string uid = temp.substr(0, i); + string pwd = temp.substr(i + 1); + connection.setUserId(uid); + } + } else { + connection.setUserId("anonymous"); + } + client.tune(framing::CHANNEL_MAX, connection.getFrameMax(), 0, 0); +} + + +#if HAVE_SASL + +CyrusAuthenticator::CyrusAuthenticator(Connection& c) : sasl_conn(0), connection(c), client(c.getOutput()) +{ + init(); +} + +void CyrusAuthenticator::init() +{ + /* Next to the service name, which specifies the + * /etc/sasl2/<service name>.conf file to read, the realm is + * currently the most important argument below. When + * performing authentication the user that is authenticating + * will be looked up in a specific realm. If none is given + * then the realm defaults to the hostname, which can cause + * confusion when the daemon is run on different hosts that + * may be logically sharing a realm (aka a user domain). This + * is especially important for SASL PLAIN authentication, + * which cannot specify a realm for the user that is + * authenticating. + */ + const char *realm = connection.getBroker().getOptions().realm.c_str(); + int code = sasl_server_new(BROKER_SASL_NAME, /* Service name */ + NULL, /* Server FQDN, gethostname() */ + realm, /* Authentication realm */ + NULL, /* Local IP, needed for some mechanism */ + NULL, /* Remote IP, needed for some mechanism */ + NULL, /* Callbacks */ + 0, /* Connection flags */ + &sasl_conn); + + if (SASL_OK != code) { + QPID_LOG(info, "SASL: Connection creation failed: [" << code << "] " << sasl_errdetail(sasl_conn)); + + // TODO: Change this to an exception signaling + // server error, when one is available + throw ConnectionForcedException("Unable to perform authentication"); + } +} + +CyrusAuthenticator::~CyrusAuthenticator() +{ + if (sasl_conn) { + sasl_dispose(&sasl_conn); + sasl_conn = 0; + } +} + +void CyrusAuthenticator::getMechanisms(Array& mechanisms) +{ + const char *separator = " "; + const char *list; + unsigned int list_len; + int count; + + int code = sasl_listmech(sasl_conn, NULL, + "", separator, "", + &list, &list_len, + &count); + + if (SASL_OK != code) { + QPID_LOG(info, "SASL: Mechanism listing failed: " << sasl_errdetail(sasl_conn)); + + // TODO: Change this to an exception signaling + // server error, when one is available + throw ConnectionForcedException("Mechanism listing failed"); + } else { + string mechanism; + unsigned int start; + unsigned int end; + + QPID_LOG(info, "SASL: Mechanism list: " << list); + + end = 0; + do { + start = end; + + // Seek to end of next mechanism + while (end < list_len && separator[0] != list[end]) + end++; + + // Record the mechanism + mechanisms.add(boost::shared_ptr<FieldValue>(new Str16Value(string(list, start, end - start)))); + end++; + } while (end < list_len); + } +} + +void CyrusAuthenticator::start(const string& mechanism, const string& response) +{ + const char *challenge; + unsigned int challenge_len; + + QPID_LOG(info, "SASL: Starting authentication with mechanism: " << mechanism); + int code = sasl_server_start(sasl_conn, + mechanism.c_str(), + response.c_str(), response.length(), + &challenge, &challenge_len); + + processAuthenticationStep(code, challenge, challenge_len); +} + +void CyrusAuthenticator::step(const string& response) +{ + const char *challenge; + unsigned int challenge_len; + + int code = sasl_server_step(sasl_conn, + response.c_str(), response.length(), + &challenge, &challenge_len); + + processAuthenticationStep(code, challenge, challenge_len); +} + +void CyrusAuthenticator::processAuthenticationStep(int code, const char *challenge, unsigned int challenge_len) +{ + if (SASL_OK == code) { + const void *uid; + + code = sasl_getprop(sasl_conn, SASL_USERNAME, &uid); + if (SASL_OK != code) { + QPID_LOG(info, "SASL: Authentication succeeded, username unavailable"); + // TODO: Change this to an exception signaling + // authentication failure, when one is available + throw ConnectionForcedException("Authenticated username unavailable"); + } + + QPID_LOG(info, "SASL: Authentication succeeded for: " + << const_cast<char*>(static_cast<const char*>(uid))); + + connection.setUserId(const_cast<char*>(static_cast<const char*>(uid))); + + client.tune(framing::CHANNEL_MAX, connection.getFrameMax(), 0, 0); + } else if (SASL_CONTINUE == code) { + string challenge_str(challenge, challenge_len); + + QPID_LOG(debug, "SASL: sending challenge to client"); + + client.secure(challenge_str); + } else { + QPID_LOG(info, "SASL: Authentication failed: " << sasl_errdetail(sasl_conn)); + + // TODO: Change to more specific exceptions, when they are + // available + switch (code) { + case SASL_NOMECH: + throw ConnectionForcedException("Unsupported mechanism"); + break; + case SASL_TRYAGAIN: + throw ConnectionForcedException("Transient failure, try again"); + break; + default: + throw ConnectionForcedException("Authentication failed"); + break; + } + } +} +#endif + +}} |