/* * * 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 #include #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(new SaslFactory()); } return *instance; } std::auto_ptr SaslFactory::create(const std::string& username, const std::string& password, const std::string&, const std::string&, int, int, bool) { std::auto_ptr client(new NullSaslClient(username, password)); return client; } std::auto_ptr SaslFactory::createServer(const std::string& realm, const std::string& /*service*/, bool /*encryptionRequired*/, const qpid::sys::SecuritySettings&) { std::auto_ptr server(new NullSaslServer(realm)); return server; } qpid::sys::Mutex SaslFactory::lock; std::auto_ptr 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 #include 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 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::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 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(new SaslFactory()); } return *instance; } std::auto_ptr 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(new CyrusSasl(username, password, serviceName, hostName, minSsf, maxSsf, allowInteraction)); return sasl; } std::auto_ptr SaslFactory::createServer(const std::string& realm, const std::string& service, bool encryptionRequired, const qpid::sys::SecuritySettings& external) { std::auto_ptr 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 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(value)); std::auto_ptr securityLayer; if (ssf) { QPID_LOG(info, "Installing security layer, SSF: "<< ssf); securityLayer = std::auto_ptr(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(ptr); } else { QPID_LOG(warning, "Failed to retrieve sasl username"); } return userid; } std::auto_ptr 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(value)); std::auto_ptr securityLayer; if (ssf) { securityLayer = std::auto_ptr(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 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