| /* |
| * |
| * 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 "qpid/SaslFactory.h" |
| #include "qpid/SaslServer.h" |
| #include "qpid/NullSaslClient.h" |
| #include "qpid/NullSaslServer.h" |
| #include <map> |
| #include <string.h> |
| |
| #include "config.h" |
| |
| #ifndef HAVE_SASL |
| |
| namespace qpid { |
| |
| //Null implementation |
| |
| |
| SaslFactory::SaslFactory() {} |
| |
| SaslFactory::~SaslFactory() {} |
| |
| SaslFactory& SaslFactory::getInstance() |
| { |
| qpid::sys::Mutex::ScopedLock l(lock); |
| if (!instance.get()) { |
| instance = std::auto_ptr<SaslFactory>(new SaslFactory()); |
| } |
| return *instance; |
| } |
| |
| std::auto_ptr<Sasl> SaslFactory::create(const std::string& username, const std::string& password, const std::string&, const std::string&, int, int, bool) |
| { |
| std::auto_ptr<Sasl> client(new NullSaslClient(username, password)); |
| return client; |
| } |
| |
| std::auto_ptr<SaslServer> SaslFactory::createServer(const std::string& realm, const std::string& /*service*/, bool /*encryptionRequired*/, const qpid::sys::SecuritySettings&) |
| { |
| std::auto_ptr<SaslServer> server(new NullSaslServer(realm)); |
| return server; |
| } |
| |
| qpid::sys::Mutex SaslFactory::lock; |
| std::auto_ptr<SaslFactory> SaslFactory::instance; |
| |
| } // namespace qpid |
| |
| #else |
| |
| #include "qpid/Exception.h" |
| #include "qpid/framing/reply_exceptions.h" |
| #include "qpid/sys/SecurityLayer.h" |
| #include "qpid/sys/SecuritySettings.h" |
| #include "qpid/sys/cyrus/CyrusSecurityLayer.h" |
| #include "qpid/log/Statement.h" |
| #include <sasl/sasl.h> |
| #include <strings.h> |
| |
| namespace qpid { |
| |
| using qpid::sys::SecurityLayer; |
| using qpid::sys::SecuritySettings; |
| using qpid::sys::cyrus::CyrusSecurityLayer; |
| using qpid::framing::InternalErrorException; |
| |
| const size_t MAX_LOGIN_LENGTH = 50; |
| |
| struct CyrusSaslSettings |
| { |
| CyrusSaslSettings ( ) : |
| username ( std::string(0) ), |
| password ( std::string(0) ), |
| service ( std::string(0) ), |
| host ( std::string(0) ), |
| minSsf ( 0 ), |
| maxSsf ( 0 ) |
| { |
| } |
| |
| CyrusSaslSettings ( const std::string & user, const std::string & password, const std::string & service, const std::string & host, int minSsf, int maxSsf ) : |
| username(user), |
| password(password), |
| service(service), |
| host(host), |
| minSsf(minSsf), |
| maxSsf(maxSsf) |
| { |
| } |
| |
| std::string username, |
| password, |
| service, |
| host; |
| |
| int minSsf, |
| maxSsf; |
| }; |
| |
| |
| class CyrusSasl : public Sasl |
| { |
| public: |
| CyrusSasl(const std::string & username, const std::string & password, const std::string & serviceName, const std::string & hostName, int minSsf, int maxSsf, bool allowInteraction); |
| ~CyrusSasl(); |
| bool start(const std::string& mechanisms, std::string& response, const SecuritySettings* externalSettings); |
| std::string step(const std::string& challenge); |
| std::string getMechanism(); |
| std::string getUserId(); |
| std::auto_ptr<SecurityLayer> getSecurityLayer(uint16_t maxFrameSize); |
| private: |
| sasl_conn_t* conn; |
| sasl_callback_t callbacks[5];//realm, user, authname, password, end-of-list |
| CyrusSaslSettings settings; |
| std::string input; |
| std::string mechanism; |
| char login[MAX_LOGIN_LENGTH]; |
| |
| /* In some contexts, like running in the broker or as a daemon, console |
| * interaction is impossible. In those cases, we will treat the attempt |
| * to interact as an error. */ |
| bool allowInteraction; |
| void interact(sasl_interact_t* client_interact); |
| }; |
| |
| //sasl callback functions |
| int getUserFromSettings(void *context, int id, const char **result, unsigned *len); |
| int getPasswordFromSettings(sasl_conn_t *conn, void *context, int id, sasl_secret_t **psecret); |
| typedef int CallbackProc(); |
| |
| qpid::sys::Mutex SaslFactory::lock; |
| std::auto_ptr<SaslFactory> SaslFactory::instance; |
| |
| class CyrusSaslServer : public SaslServer |
| { |
| public: |
| CyrusSaslServer(const std::string& realm, const std::string& service, bool encryptionRequired, const qpid::sys::SecuritySettings& external); |
| ~CyrusSaslServer(); |
| Status start(const std::string& mechanism, const std::string* response, std::string& challenge); |
| Status step(const std::string* response, std::string& challenge); |
| std::string getMechanisms(); |
| std::string getUserid(); |
| std::auto_ptr<qpid::sys::SecurityLayer> getSecurityLayer(size_t); |
| private: |
| std::string realm; |
| std::string service; |
| std::string userid; |
| sasl_conn_t *sasl_conn; |
| }; |
| |
| SaslFactory::SaslFactory() |
| { |
| sasl_callback_t* callbacks = 0; |
| int result = sasl_client_init(callbacks); |
| if (result != SASL_OK) { |
| throw InternalErrorException(QPID_MSG("Sasl error: " << sasl_errstring(result, 0, 0))); |
| } |
| } |
| |
| SaslFactory::~SaslFactory() |
| { |
| sasl_done(); |
| } |
| |
| SaslFactory& SaslFactory::getInstance() |
| { |
| qpid::sys::Mutex::ScopedLock l(lock); |
| if (!instance.get()) { |
| instance = std::auto_ptr<SaslFactory>(new SaslFactory()); |
| } |
| return *instance; |
| } |
| |
| std::auto_ptr<Sasl> SaslFactory::create(const std::string & username, const std::string & password, const std::string & serviceName, const std::string & hostName, int minSsf, int maxSsf, bool allowInteraction) |
| { |
| std::auto_ptr<Sasl> sasl(new CyrusSasl(username, password, serviceName, hostName, minSsf, maxSsf, allowInteraction)); |
| return sasl; |
| } |
| |
| std::auto_ptr<SaslServer> SaslFactory::createServer(const std::string& realm, const std::string& service, bool encryptionRequired, const qpid::sys::SecuritySettings& external) |
| { |
| std::auto_ptr<SaslServer> server(new CyrusSaslServer(realm, service, encryptionRequired, external)); |
| return server; |
| } |
| |
| CyrusSasl::CyrusSasl(const std::string & username, const std::string & password, const std::string & serviceName, const std::string & hostName, int minSsf, int maxSsf, bool allowInteraction) |
| : conn(0), settings(username, password, serviceName, hostName, minSsf, maxSsf), allowInteraction(allowInteraction) |
| { |
| size_t i = 0; |
| |
| callbacks[i].id = SASL_CB_GETREALM; |
| callbacks[i].proc = 0; |
| callbacks[i++].context = 0; |
| |
| if (!settings.username.empty()) { |
| callbacks[i].id = SASL_CB_AUTHNAME; |
| callbacks[i].proc = (CallbackProc*) &getUserFromSettings; |
| callbacks[i++].context = &settings; |
| |
| callbacks[i].id = SASL_CB_PASS; |
| if (settings.password.empty()) { |
| callbacks[i].proc = 0; |
| callbacks[i++].context = 0; |
| } else { |
| callbacks[i].proc = (CallbackProc*) &getPasswordFromSettings; |
| callbacks[i++].context = &settings; |
| } |
| } |
| |
| |
| callbacks[i].id = SASL_CB_LIST_END; |
| callbacks[i].proc = 0; |
| callbacks[i++].context = 0; |
| } |
| |
| CyrusSasl::~CyrusSasl() |
| { |
| if (conn) { |
| sasl_dispose(&conn); |
| } |
| } |
| |
| namespace { |
| const std::string SSL("ssl"); |
| } |
| |
| bool CyrusSasl::start(const std::string& mechanisms, std::string& response, const SecuritySettings* externalSettings) |
| { |
| QPID_LOG(debug, "CyrusSasl::start(" << mechanisms << ")"); |
| int result = sasl_client_new(settings.service.c_str(), |
| settings.host.c_str(), |
| 0, 0, /* Local and remote IP address strings */ |
| callbacks, |
| 0, /* security flags */ |
| &conn); |
| |
| if (result != SASL_OK) throw InternalErrorException(QPID_MSG("Sasl error: " << sasl_errdetail(conn))); |
| |
| sasl_security_properties_t secprops; |
| |
| if (externalSettings) { |
| sasl_ssf_t external_ssf = (sasl_ssf_t) externalSettings->ssf; |
| if (external_ssf) { |
| int result = sasl_setprop(conn, SASL_SSF_EXTERNAL, &external_ssf); |
| if (result != SASL_OK) { |
| throw framing::InternalErrorException(QPID_MSG("SASL error: unable to set external SSF: " << result)); |
| } |
| QPID_LOG(debug, "external SSF detected and set to " << external_ssf); |
| } |
| if (externalSettings->authid.size()) { |
| const char* external_authid = externalSettings->authid.c_str(); |
| result = sasl_setprop(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.min_ssf = settings.minSsf; |
| secprops.max_ssf = settings.maxSsf; |
| secprops.maxbufsize = 65535; |
| |
| QPID_LOG(debug, "min_ssf: " << secprops.min_ssf << ", max_ssf: " << secprops.max_ssf); |
| |
| secprops.property_names = 0; |
| secprops.property_values = 0; |
| secprops.security_flags = 0;//TODO: provide means for application to configure these |
| |
| result = sasl_setprop(conn, SASL_SEC_PROPS, &secprops); |
| if (result != SASL_OK) { |
| throw framing::InternalErrorException(QPID_MSG("SASL error: " << sasl_errdetail(conn))); |
| } |
| |
| sasl_interact_t* client_interact = 0; |
| const char *out = 0; |
| unsigned outlen = 0; |
| const char *chosenMechanism = 0; |
| |
| do { |
| result = sasl_client_start(conn, |
| mechanisms.c_str(), |
| &client_interact, |
| &out, |
| &outlen, |
| &chosenMechanism); |
| |
| if (result == SASL_INTERACT) { |
| interact(client_interact); |
| } |
| } while (result == SASL_INTERACT); |
| |
| if (result == SASL_NOMECH) { |
| if (mechanisms.size()) { |
| throw qpid::Exception(std::string("Can't authenticate using ") + mechanisms); |
| } else { |
| throw qpid::Exception("No mutually acceptable authentication mechanism"); |
| } |
| } else if (result != SASL_CONTINUE && result != SASL_OK) { |
| throw InternalErrorException(QPID_MSG("Sasl error: " << sasl_errdetail(conn))); |
| } |
| |
| mechanism = std::string(chosenMechanism); |
| QPID_LOG(debug, "CyrusSasl::start(" << mechanisms << "): selected " |
| << mechanism << " response: '" << std::string(out, outlen) << "'"); |
| if (out) { |
| response = std::string(out, outlen); |
| return true; |
| } else { |
| return false; |
| } |
| } |
| |
| std::string CyrusSasl::step(const std::string& challenge) |
| { |
| sasl_interact_t* client_interact = 0; |
| const char *out = 0; |
| unsigned outlen = 0; |
| int result = 0; |
| do { |
| result = sasl_client_step(conn, /* our context */ |
| challenge.data(), /* the data from the server */ |
| challenge.size(), /* it's length */ |
| &client_interact, /* this should be |
| unallocated and NULL */ |
| &out, /* filled in on success */ |
| &outlen); /* filled in on success */ |
| |
| if (result == SASL_INTERACT) { |
| interact(client_interact); |
| } |
| } while (result == SASL_INTERACT); |
| |
| std::string response; |
| if (result == SASL_CONTINUE || result == SASL_OK) response = std::string(out, outlen); |
| else if (result != SASL_OK) { |
| throw InternalErrorException(QPID_MSG("Sasl error: " << sasl_errdetail(conn))); |
| } |
| QPID_LOG(debug, "CyrusSasl::step(" << challenge << "): " << response); |
| return response; |
| } |
| |
| std::string CyrusSasl::getMechanism() |
| { |
| return mechanism; |
| } |
| |
| std::string CyrusSasl::getUserId() |
| { |
| int propResult; |
| const void* operName; |
| |
| propResult = sasl_getprop(conn, SASL_USERNAME, &operName); |
| if (propResult == SASL_OK) |
| return std::string((const char*) operName); |
| |
| return std::string(); |
| } |
| |
| void CyrusSasl::interact(sasl_interact_t* client_interact) |
| { |
| |
| /* |
| In some context console interaction cannot be allowed, such |
| as when this code run as part of a broker, or as a some other |
| daemon. In those cases we will treat the attempt to |
| */ |
| if ( ! allowInteraction ) { |
| throw InternalErrorException("interaction disallowed"); |
| } |
| |
| if (client_interact->id == SASL_CB_PASS) { |
| char* password = getpass(client_interact->prompt); |
| input = std::string(password); |
| client_interact->result = input.data(); |
| client_interact->len = input.size(); |
| } else { |
| std::cout << client_interact->prompt; |
| if (client_interact->defresult) std::cout << " (" << client_interact->defresult << ")"; |
| std::cout << ": "; |
| if (std::cin >> input) { |
| client_interact->result = input.data(); |
| client_interact->len = input.size(); |
| } |
| } |
| |
| } |
| |
| std::auto_ptr<SecurityLayer> CyrusSasl::getSecurityLayer(uint16_t maxFrameSize) |
| { |
| const void* value(0); |
| int result = sasl_getprop(conn, SASL_SSF, &value); |
| if (result != SASL_OK) { |
| throw framing::InternalErrorException(QPID_MSG("SASL error: " << sasl_errdetail(conn))); |
| } |
| uint ssf = *(reinterpret_cast<const unsigned*>(value)); |
| std::auto_ptr<SecurityLayer> securityLayer; |
| if (ssf) { |
| QPID_LOG(info, "Installing security layer, SSF: "<< ssf); |
| securityLayer = std::auto_ptr<SecurityLayer>(new CyrusSecurityLayer(conn, maxFrameSize, ssf)); |
| } |
| return securityLayer; |
| } |
| |
| CyrusSaslServer::CyrusSaslServer(const std::string& r, const std::string& s, bool encryptionRequired, const qpid::sys::SecuritySettings& external) : realm(r), service(s), sasl_conn(0) |
| { |
| int code = sasl_server_new(service.c_str(), /* Service name */ |
| NULL, /* Server FQDN, gethostname() */ |
| realm.c_str(), /* 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 qpid::framing::ConnectionForcedException("Unable to perform authentication"); |
| } |
| |
| sasl_security_properties_t secprops; |
| |
| //TODO: should the actual SSF values be configurable here? |
| secprops.min_ssf = encryptionRequired ? 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. |
| 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)); |
| } |
| } |
| |
| CyrusSaslServer::~CyrusSaslServer() |
| { |
| if (sasl_conn) { |
| sasl_dispose(&sasl_conn); |
| sasl_conn = 0; |
| } |
| } |
| |
| CyrusSaslServer::Status CyrusSaslServer::start(const std::string& mechanism, const std::string* response, std::string& chllng) |
| { |
| 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); |
| switch (code) { |
| case SASL_OK: |
| return SaslServer::OK; |
| case SASL_CONTINUE: |
| chllng = std::string(challenge, challenge_len); |
| return SaslServer::CHALLENGE; |
| case SASL_NOMECH: |
| QPID_LOG(info, "Unsupported mechanism: " << mechanism); |
| default: |
| return SaslServer::FAIL; |
| } |
| } |
| |
| CyrusSaslServer::Status CyrusSaslServer::step(const std::string* response, std::string& chllng) |
| { |
| const char *challenge; |
| unsigned int challenge_len; |
| |
| int code = sasl_server_step(sasl_conn, |
| (response ? response->c_str() : 0), (response ? response->size() : 0), |
| &challenge, &challenge_len); |
| |
| switch (code) { |
| case SASL_OK: |
| return SaslServer::OK; |
| case SASL_CONTINUE: |
| chllng = std::string(challenge, challenge_len); |
| return SaslServer::CHALLENGE; |
| default: |
| return SaslServer::FAIL; |
| } |
| |
| } |
| std::string CyrusSaslServer::getMechanisms() |
| { |
| 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 qpid::framing::ConnectionForcedException("Mechanism listing failed"); |
| } else { |
| std::string mechanisms(list, list_len); |
| QPID_LOG(info, "SASL: Mechanism list: " << mechanisms); |
| return mechanisms; |
| } |
| } |
| std::string CyrusSaslServer::getUserid() |
| { |
| const void* ptr; |
| int code = sasl_getprop(sasl_conn, SASL_USERNAME, &ptr); |
| if (SASL_OK == code) { |
| userid = static_cast<const char*>(ptr); |
| } else { |
| QPID_LOG(warning, "Failed to retrieve sasl username"); |
| } |
| return userid; |
| } |
| |
| std::auto_ptr<SecurityLayer> CyrusSaslServer::getSecurityLayer(size_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, ssf)); |
| } |
| return securityLayer; |
| } |
| |
| int getUserFromSettings(void* context, int /*id*/, const char** result, unsigned* /*len*/) |
| { |
| if (context) { |
| *result = ((CyrusSaslSettings*) context)->username.c_str(); |
| QPID_LOG(debug, "getUserFromSettings(): " << (*result)); |
| return SASL_OK; |
| } else { |
| return SASL_FAIL; |
| } |
| } |
| |
| namespace { |
| // Global map of secrets allocated for SASL connections via callback |
| // to getPasswordFromSettings. Ensures secrets are freed. |
| class SecretsMap { |
| typedef std::map<sasl_conn_t*, void*> Map; |
| Map map; |
| sys::Mutex lock; |
| public: |
| void keep(sasl_conn_t* conn, void* secret) { |
| sys::Mutex::ScopedLock l(lock); |
| Map::iterator i = map.find(conn); |
| if (i != map.end()) free(i->second); |
| map[conn] = secret; |
| } |
| |
| ~SecretsMap() { |
| for (Map::iterator i = map.begin(); i != map.end(); ++i) |
| free(i->second); |
| } |
| }; |
| SecretsMap getPasswordFromSettingsSecrets; |
| } |
| |
| int getPasswordFromSettings(sasl_conn_t* conn, void* context, int /*id*/, sasl_secret_t** psecret) |
| { |
| if (context) { |
| size_t length = ((CyrusSaslSettings*) context)->password.size(); |
| sasl_secret_t* secret = (sasl_secret_t*) malloc(sizeof(sasl_secret_t) + length); |
| getPasswordFromSettingsSecrets.keep(conn, secret); |
| secret->len = length; |
| memcpy(secret->data, ((CyrusSaslSettings*) context)->password.data(), length); |
| *psecret = secret; |
| return SASL_OK; |
| } else { |
| return SASL_FAIL; |
| } |
| } |
| } // namespace qpid |
| |
| #endif |