blob: 370de8a1d1b92e922d9e966bdfaaaf0f2a9393f7 [file] [log] [blame]
/*
*
* 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.
*
*/
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif
#include "Connection.h"
#include "qpid/log/Statement.h"
#include "qpid/framing/reply_exceptions.h"
#include <boost/format.hpp>
#if HAVE_SASL
#include <sasl/sasl.h>
#endif
using namespace qpid::framing;
using boost::format;
using boost::str;
namespace qpid {
namespace broker {
class NullAuthenticator : public SaslAuthenticator
{
Connection& connection;
framing::AMQP_ClientProxy::Connection client;
std::string realm;
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);
void getUid(std::string& uid);
void getError(std::string& error);
};
bool SaslAuthenticator::available(void)
{
return true;
}
// Initialize the SASL mechanism; throw if it fails.
void SaslAuthenticator::init(const std::string& saslName)
{
int code = sasl_server_init(NULL, saslName.c_str());
if (code != SASL_OK) {
// TODO: Figure out who owns the char* returned by
// sasl_errstring, though it probably does not matter much
throw Exception(sasl_errstring(code, NULL, NULL));
}
}
void SaslAuthenticator::fini(void)
{
sasl_done();
}
#else
typedef NullAuthenticator CyrusAuthenticator;
bool SaslAuthenticator::available(void)
{
return false;
}
void SaslAuthenticator::init(const std::string& /*saslName*/)
{
throw Exception("Requested authentication but SASL unavailable");
}
void SaslAuthenticator::fini(void)
{
return;
}
#endif
std::auto_ptr<SaslAuthenticator> SaslAuthenticator::createAuthenticator(Connection& c)
{
static bool needWarning = true;
if (c.getBroker().getOptions().auth) {
return std::auto_ptr<SaslAuthenticator>(new CyrusAuthenticator(c));
} else {
QPID_LOG(warning, "SASL: No Authentication Performed");
needWarning = false;
return std::auto_ptr<SaslAuthenticator>(new NullAuthenticator(c));
}
}
NullAuthenticator::NullAuthenticator(Connection& c) : connection(c), client(c.getOutput()),
realm(c.getBroker().getOptions().realm) {}
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)
{
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);
i = uid.find_last_of(realm);
if (i == string::npos || i != (uid.size() - 1)) {
uid = str(format("%1%@%2%") % uid % realm);
}
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::getError(string& error)
{
error = string(sasl_errdetail(sasl_conn));
}
void CyrusAuthenticator::getUid(string& uid)
{
int code;
const void* ptr;
code = sasl_getprop(sasl_conn, SASL_USERNAME, &ptr);
if (SASL_OK != code)
return;
uid = string(const_cast<char*>(static_cast<const char*>(ptr)));
}
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
}}