/* * * 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 #include #ifdef HAVE_CONFIG_H # include "config.h" #endif #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 &, const std::string &, const std::string &, const std::string &, int, int, bool ) { return std::auto_ptr(); } 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(); std::string start(const std::string& mechanisms, 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; 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; } 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"); } std::string CyrusSasl::start(const std::string& mechanisms, 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_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) << "'"); return std::string(out, outlen); } 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)); } 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; public: void keep(sasl_conn_t* conn, void* secret) { 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