| /* |
| * |
| * 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 "qpid/broker/Connection.h" |
| #include "qpid/log/Statement.h" |
| #include "qpid/framing/reply_exceptions.h" |
| #include "qpid/sys/SecuritySettings.h" |
| #include <boost/format.hpp> |
| |
| #if HAVE_SASL |
| #include <sys/stat.h> |
| #include <sasl/sasl.h> |
| #include "qpid/sys/cyrus/CyrusSecurityLayer.h" |
| using qpid::sys::cyrus::CyrusSecurityLayer; |
| #endif |
| |
| using namespace qpid::framing; |
| using qpid::sys::SecurityLayer; |
| using qpid::sys::SecuritySettings; |
| using boost::format; |
| using boost::str; |
| |
| |
| namespace qpid { |
| namespace broker { |
| |
| |
| |
| class NullAuthenticator : public SaslAuthenticator |
| { |
| Connection& connection; |
| framing::AMQP_ClientProxy::Connection client; |
| std::string realm; |
| const bool encrypt; |
| public: |
| NullAuthenticator(Connection& connection, bool encrypt); |
| ~NullAuthenticator(); |
| void getMechanisms(framing::Array& mechanisms); |
| void start(const std::string& mechanism, const std::string* response); |
| void step(const std::string&) {} |
| std::auto_ptr<SecurityLayer> getSecurityLayer(uint16_t maxFrameSize); |
| }; |
| |
| #if HAVE_SASL |
| |
| |
| |
| class CyrusAuthenticator : public SaslAuthenticator |
| { |
| sasl_conn_t *sasl_conn; |
| Connection& connection; |
| framing::AMQP_ClientProxy::Connection client; |
| const bool encrypt; |
| |
| void processAuthenticationStep(int code, const char *challenge, unsigned int challenge_len); |
| bool getUsername(std::string& uid); |
| |
| public: |
| CyrusAuthenticator(Connection& connection, bool encrypt); |
| ~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 getError(std::string& error); |
| void getUid(std::string& uid) { getUsername(uid); } |
| std::auto_ptr<SecurityLayer> getSecurityLayer(uint16_t maxFrameSize); |
| }; |
| |
| bool SaslAuthenticator::available(void) { |
| return true; |
| } |
| |
| // Initialize the SASL mechanism; throw if it fails. |
| void SaslAuthenticator::init(const std::string& saslName, std::string const & saslConfigPath ) |
| { |
| // Check if we have a version of SASL that supports sasl_set_path() |
| #if (SASL_VERSION_FULL >= ((2<<16)|(1<<8)|22)) |
| // If we are not given a sasl path, do nothing and allow the default to be used. |
| if ( saslConfigPath.empty() ) { |
| QPID_LOG ( info, "SASL: no config path set - using default." ); |
| } |
| else { |
| struct stat st; |
| |
| // Make sure the directory exists and we can read up to it. |
| if ( ::stat ( saslConfigPath.c_str(), & st) ) { |
| // Note: not using strerror() here because I think its messages are a little too hazy. |
| if ( errno == ENOENT ) |
| throw Exception ( QPID_MSG ( "SASL: sasl_set_path failed: no such directory: " << saslConfigPath ) ); |
| if ( errno == EACCES ) |
| throw Exception ( QPID_MSG ( "SASL: sasl_set_path failed: cannot read parent of: " << saslConfigPath ) ); |
| // catch-all stat failure |
| throw Exception ( QPID_MSG ( "SASL: sasl_set_path failed: cannot stat: " << saslConfigPath ) ); |
| } |
| |
| // Make sure the directory is readable. |
| if ( ::access ( saslConfigPath.c_str(), R_OK ) ) { |
| throw Exception ( QPID_MSG ( "SASL: sasl_set_path failed: directory not readable:" << saslConfigPath ) ); |
| } |
| |
| // This shouldn't fail now, but check anyway. |
| int code = sasl_set_path(SASL_PATH_TYPE_CONFIG, const_cast<char *>(saslConfigPath.c_str())); |
| if(SASL_OK != code) |
| throw Exception(QPID_MSG("SASL: sasl_set_path failed [" << code << "] " )); |
| |
| QPID_LOG(info, "SASL: config path set to " << saslConfigPath ); |
| } |
| #endif |
| |
| 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*/, std::string const & /*saslConfigPath*/ ) |
| { |
| throw Exception("Requested authentication but SASL unavailable"); |
| } |
| |
| void SaslAuthenticator::fini(void) |
| { |
| return; |
| } |
| |
| #endif |
| |
| std::auto_ptr<SaslAuthenticator> SaslAuthenticator::createAuthenticator(Connection& c, bool isShadow ) |
| { |
| if (c.getBroker().getOptions().auth) { |
| if ( isShadow ) |
| return std::auto_ptr<SaslAuthenticator>(new NullAuthenticator(c, c.getBroker().getOptions().requireEncrypted)); |
| else |
| return std::auto_ptr<SaslAuthenticator>(new CyrusAuthenticator(c, c.getBroker().getOptions().requireEncrypted)); |
| } else { |
| QPID_LOG(debug, "SASL: No Authentication Performed"); |
| return std::auto_ptr<SaslAuthenticator>(new NullAuthenticator(c, c.getBroker().getOptions().requireEncrypted)); |
| } |
| } |
| |
| |
| NullAuthenticator::NullAuthenticator(Connection& c, bool e) : connection(c), client(c.getOutput()), |
| realm(c.getBroker().getOptions().realm), encrypt(e) {} |
| NullAuthenticator::~NullAuthenticator() {} |
| |
| void NullAuthenticator::getMechanisms(Array& mechanisms) |
| { |
| mechanisms.add(boost::shared_ptr<FieldValue>(new Str16Value("ANONYMOUS"))); |
| mechanisms.add(boost::shared_ptr<FieldValue>(new Str16Value("PLAIN")));//useful for testing |
| } |
| |
| void NullAuthenticator::start(const string& mechanism, const string* response) |
| { |
| if (encrypt) { |
| #if HAVE_SASL |
| // encryption required - check to see if we are running over an |
| // encrypted SSL connection. |
| SecuritySettings external = connection.getExternalSecuritySettings(); |
| sasl_ssf_t external_ssf = (sasl_ssf_t) external.ssf; |
| if (external_ssf < 1) // < 1 == unencrypted |
| #endif |
| { |
| QPID_LOG(error, "Rejected un-encrypted connection."); |
| throw ConnectionForcedException("Connection must be encrypted."); |
| } |
| } |
| if (mechanism == "PLAIN") { // Old behavior |
| if (response && response->size() > 0) { |
| string uid; |
| string::size_type i = response->find((char)0); |
| if (i == 0 && response->size() > 1) { |
| //no authorization id; use authentication id |
| i = response->find((char)0, 1); |
| if (i != string::npos) uid = response->substr(1, i-1); |
| } else if (i != string::npos) { |
| //authorization id is first null delimited field |
| uid = response->substr(0, i); |
| }//else not a valid SASL PLAIN response, throw error? |
| if (!uid.empty()) { |
| //append realm if it has not already been added |
| i = uid.find(realm); |
| if (i == string::npos || realm.size() + i < uid.size()) { |
| uid = str(format("%1%@%2%") % uid % realm); |
| } |
| connection.setUserId(uid); |
| } |
| } |
| } else { |
| connection.setUserId("anonymous"); |
| } |
| client.tune(framing::CHANNEL_MAX, connection.getFrameMax(), 0, connection.getHeartbeatMax()); |
| } |
| |
| |
| std::auto_ptr<SecurityLayer> NullAuthenticator::getSecurityLayer(uint16_t) |
| { |
| std::auto_ptr<SecurityLayer> securityLayer; |
| return securityLayer; |
| } |
| |
| |
| #if HAVE_SASL |
| |
| CyrusAuthenticator::CyrusAuthenticator(Connection& c, bool _encrypt) : |
| sasl_conn(0), connection(c), client(c.getOutput()), encrypt(_encrypt) |
| { |
| 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. |
| */ |
| int code; |
| |
| const char *realm = connection.getBroker().getOptions().realm.c_str(); |
| 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(error, "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"); |
| } |
| |
| sasl_security_properties_t secprops; |
| |
| //TODO: should the actual SSF values be configurable here? |
| secprops.min_ssf = encrypt ? 10: 0; |
| secprops.max_ssf = 256; |
| |
| // If the transport provides encryption, notify the SASL library of |
| // the key length and set the ssf range to prevent double encryption. |
| SecuritySettings external = connection.getExternalSecuritySettings(); |
| QPID_LOG(debug, "External ssf=" << external.ssf << " and auth=" << external.authid); |
| sasl_ssf_t external_ssf = (sasl_ssf_t) external.ssf; |
| if (external_ssf) { |
| int result = sasl_setprop(sasl_conn, SASL_SSF_EXTERNAL, &external_ssf); |
| if (result != SASL_OK) { |
| throw framing::InternalErrorException(QPID_MSG("SASL error: unable to set external SSF: " << result)); |
| } |
| |
| secprops.max_ssf = secprops.min_ssf = 0; |
| } |
| |
| QPID_LOG(debug, "min_ssf: " << secprops.min_ssf << |
| ", max_ssf: " << secprops.max_ssf << |
| ", external_ssf: " << external_ssf ); |
| |
| if (!external.authid.empty()) { |
| const char* external_authid = external.authid.c_str(); |
| int result = sasl_setprop(sasl_conn, SASL_AUTH_EXTERNAL, external_authid); |
| if (result != SASL_OK) { |
| throw framing::InternalErrorException(QPID_MSG("SASL error: unable to set external auth: " << result)); |
| } |
| |
| QPID_LOG(debug, "external auth detected and set to " << external_authid); |
| } |
| |
| secprops.maxbufsize = 65535; |
| secprops.property_names = 0; |
| secprops.property_values = 0; |
| secprops.security_flags = 0; /* or SASL_SEC_NOANONYMOUS etc as appropriate */ |
| /* |
| * The nodict flag restricts SASL authentication mechanisms |
| * to those that are not susceptible to dictionary attacks. |
| * They are: |
| * SRP |
| * PASSDSS-3DES-1 |
| * EXTERNAL |
| */ |
| if (external.nodict) secprops.security_flags |= SASL_SEC_NODICTIONARY; |
| int result = sasl_setprop(sasl_conn, SASL_SEC_PROPS, &secprops); |
| if (result != SASL_OK) { |
| throw framing::InternalErrorException(QPID_MSG("SASL error: " << result)); |
| } |
| } |
| |
| CyrusAuthenticator::~CyrusAuthenticator() |
| { |
| if (sasl_conn) { |
| sasl_dispose(&sasl_conn); |
| sasl_conn = 0; |
| } |
| } |
| |
| void CyrusAuthenticator::getError(string& error) |
| { |
| error = string(sasl_errdetail(sasl_conn)); |
| } |
| |
| bool CyrusAuthenticator::getUsername(string& uid) |
| { |
| const void* ptr; |
| |
| int code = sasl_getprop(sasl_conn, SASL_USERNAME, &ptr); |
| if (SASL_OK == code) { |
| uid = string(const_cast<char*>(static_cast<const char*>(ptr))); |
| return true; |
| } else { |
| QPID_LOG(warning, "Failed to retrieve sasl username"); |
| return false; |
| } |
| } |
| |
| 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; |
| |
| // This should be at same debug level as mech list in getMechanisms(). |
| QPID_LOG(info, "SASL: Starting authentication with mechanism: " << mechanism); |
| int code = sasl_server_start(sasl_conn, |
| mechanism.c_str(), |
| (response ? response->c_str() : 0), (response ? response->size() : 0), |
| &challenge, &challenge_len); |
| |
| processAuthenticationStep(code, challenge, challenge_len); |
| qmf::org::apache::qpid::broker::Connection* cnxMgmt = connection.getMgmtObject(); |
| if ( cnxMgmt ) |
| cnxMgmt->set_saslMechanism(mechanism); |
| } |
| |
| 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) { |
| std::string uid; |
| if (!getUsername(uid)) { |
| // TODO: Change this to an exception signaling |
| // authentication failure, when one is available |
| throw ConnectionForcedException("Authenticated username unavailable"); |
| } |
| QPID_LOG(info, connection.getMgmtId() << " SASL: Authentication succeeded for: " << uid); |
| |
| connection.setUserId(uid); |
| |
| client.tune(framing::CHANNEL_MAX, connection.getFrameMax(), 0, connection.getHeartbeatMax()); |
| } else if (SASL_CONTINUE == code) { |
| string challenge_str(challenge, challenge_len); |
| |
| QPID_LOG(debug, "SASL: sending challenge to client"); |
| |
| client.secure(challenge_str); |
| } else { |
| std::string uid; |
| //save error detail before trying to retrieve username as error in doing so will overwrite it |
| std::string errordetail = sasl_errdetail(sasl_conn); |
| if (!getUsername(uid)) { |
| QPID_LOG(info, "SASL: Authentication failed (no username available yet):" << errordetail); |
| } else { |
| QPID_LOG(info, "SASL: Authentication failed for " << uid << ":" << errordetail); |
| } |
| |
| // 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; |
| } |
| } |
| } |
| |
| std::auto_ptr<SecurityLayer> CyrusAuthenticator::getSecurityLayer(uint16_t maxFrameSize) |
| { |
| |
| const void* value(0); |
| int result = sasl_getprop(sasl_conn, SASL_SSF, &value); |
| if (result != SASL_OK) { |
| throw framing::InternalErrorException(QPID_MSG("SASL error: " << sasl_errdetail(sasl_conn))); |
| } |
| uint ssf = *(reinterpret_cast<const unsigned*>(value)); |
| std::auto_ptr<SecurityLayer> securityLayer; |
| if (ssf) { |
| securityLayer = std::auto_ptr<SecurityLayer>(new CyrusSecurityLayer(sasl_conn, maxFrameSize)); |
| } |
| qmf::org::apache::qpid::broker::Connection* cnxMgmt = connection.getMgmtObject(); |
| if ( cnxMgmt ) |
| cnxMgmt->set_saslSsf(ssf); |
| return securityLayer; |
| } |
| |
| #endif |
| |
| }} |