#ifndef ENABLE_GSS_TSIG
+std::tuple<size_t, size_t, size_t> GssContext::getCounts() { return std::make_tuple<size_t, size_t, size_t>(0, 0, 0); }
bool GssContext::supported() { return false; }
GssContext::GssContext() :
d_error(GSS_CONTEXT_UNSUPPORTED), d_type(GSS_CONTEXT_NONE) {}
#else
-static string gsserror(OM_uint32 status_code)
-{
- OM_uint32 maj_status;
- OM_uint32 min_status;
- OM_uint32 message_context = 0;
- gss_buffer_desc status_string;
- std::basic_ostringstream<char> ret;
- bool first = true;
- do {
- if (!first) {
- ret << '/';
- } else {
- first = false;
- }
- maj_status = gss_display_status(&min_status,
- status_code,
- GSS_C_GSS_CODE,
- GSS_C_NO_OID,
- &message_context,
- &status_string);
- if (maj_status == GSS_S_COMPLETE) {
- ret << string(static_cast<char*>(status_string.value), status_string.length);
- gss_release_buffer(&min_status, &status_string);
- } else {
- // XXX to release or not to release?
- ret << std::to_string(status_code);
- }
- } while (message_context != 0);
- return ret.str();
-}
-
class GssCredential : boost::noncopyable
{
public:
GssCredential(const std::string& name, const gss_cred_usage_t usage) :
- d_valid(false), d_nameS(name), d_name(GSS_C_NO_NAME), d_cred(GSS_C_NO_CREDENTIAL), d_usage(usage)
+ d_nameS(name), d_usage(usage)
{
gss_buffer_desc buffer;
if (!name.empty()) {
buffer.length = name.size();
buffer.value = const_cast<void*>(static_cast<const void*>(name.c_str()));
- d_maj = gss_import_name(&d_min, &buffer, (gss_OID)GSS_KRB5_NT_PRINCIPAL_NAME, &d_name);
- if (d_maj != GSS_S_COMPLETE) {
+ OM_uint32 min;
+ auto maj = gss_import_name(&min, &buffer, (gss_OID)GSS_KRB5_NT_PRINCIPAL_NAME, &d_name);
+ if (maj != GSS_S_COMPLETE) {
d_name = GSS_C_NO_NAME;
d_valid = false;
return;
~GssCredential()
{
OM_uint32 tmp_maj __attribute__((unused)), tmp_min __attribute__((unused));
- if (d_cred != GSS_C_NO_CREDENTIAL)
+ if (d_cred != GSS_C_NO_CREDENTIAL) {
tmp_maj = gss_release_cred(&tmp_min, &d_cred);
- if (d_name != GSS_C_NO_NAME)
+ }
+ if (d_name != GSS_C_NO_NAME) {
tmp_maj = gss_release_name(&tmp_min, &d_name);
+ }
};
bool expired() const
{
- if (d_expires == -1)
+ if (d_expires == -1) {
return false;
+ }
return time(nullptr) > d_expires;
}
bool renew()
{
OM_uint32 time_rec, tmp_maj __attribute__((unused)), tmp_min __attribute__((unused));
- d_maj = gss_acquire_cred(&d_min, d_name, GSS_C_INDEFINITE, GSS_C_NO_OID_SET, d_usage, &d_cred, nullptr, &time_rec);
+ tmp_maj = gss_acquire_cred(&tmp_min, d_name, GSS_C_INDEFINITE, GSS_C_NO_OID_SET, d_usage, &d_cred, nullptr, &time_rec);
- if (d_maj != GSS_S_COMPLETE) {
+ if (tmp_maj != GSS_S_COMPLETE) {
d_valid = false;
tmp_maj = gss_release_name(&tmp_min, &d_name);
d_name = GSS_C_NO_NAME;
d_valid = true;
- if (time_rec > GSS_C_INDEFINITE) {
- d_expires = time(nullptr) + time_rec;
- }
- else {
- d_expires = -1;
+ // We do not want forever, but a good time
+ if (time_rec == GSS_C_INDEFINITE) {
+ time_rec = 24 * 60 * 60;
}
+ d_expires = time(nullptr) + time_rec;
return true;
}
return d_valid && !expired();
}
- OM_uint32 d_maj, d_min;
-
- bool d_valid;
- int64_t d_expires;
std::string d_nameS;
- gss_name_t d_name;
- gss_cred_id_t d_cred;
gss_cred_usage_t d_usage;
-};
+ gss_name_t d_name{GSS_C_NO_NAME};
+ gss_cred_id_t d_cred{GSS_C_NO_CREDENTIAL};
+ time_t d_expires{time(nullptr) + 60}; // partly initialized wil be cleaned up
+ bool d_valid{false};
+}; // GssCredential
-LockGuarded<std::map<std::string, std::shared_ptr<GssCredential>>> s_gss_accept_creds;
-LockGuarded<std::map<std::string, std::shared_ptr<GssCredential>>> s_gss_init_creds;
+static LockGuarded<std::unordered_map<std::string, std::shared_ptr<GssCredential>>> s_gss_accept_creds;
+static LockGuarded<std::unordered_map<std::string, std::shared_ptr<GssCredential>>> s_gss_init_creds;
class GssSecContext : boost::noncopyable
{
public:
- GssSecContext(std::shared_ptr<GssCredential> cred)
+ GssSecContext(std::shared_ptr<GssCredential> cred) :
+ d_cred(cred)
{
if (!cred->valid()) {
- throw PDNSException("Invalid credential " + cred->d_nameS + ": " + gsserror(cred->d_maj));
+ throw PDNSException("Invalid credential " + cred->d_nameS);
}
d_cred = cred;
d_state = GssStateInitial;
d_ctx = GSS_C_NO_CONTEXT;
d_expires = 0;
- d_maj = d_min = 0;
d_peer_name = GSS_C_NO_NAME;
d_type = GSS_CONTEXT_NONE;
}
}
}
- GssContextType d_type;
- gss_ctx_id_t d_ctx;
- gss_name_t d_peer_name;
- int64_t d_expires;
std::shared_ptr<GssCredential> d_cred;
- OM_uint32 d_maj, d_min;
+ GssContextType d_type{GSS_CONTEXT_NONE};
+ gss_ctx_id_t d_ctx{GSS_C_NO_CONTEXT};
+ gss_name_t d_peer_name{GSS_C_NO_NAME};
+ time_t d_expires{time(nullptr) + 60}; // partly initialized wil be cleaned up
enum
{
GssStateComplete,
GssStateError
} d_state;
-};
+}; // GssSecContext
+
+static LockGuarded<std::unordered_map<DNSName, std::shared_ptr<GssSecContext>>> s_gss_sec_context;
-LockGuarded<std::map<DNSName, std::shared_ptr<GssSecContext>>> s_gss_sec_context;
+template <typename T>
+static void doExpire(T& m, time_t now)
+{
+ auto lock = m.lock();
+ for (auto i = lock->begin(); i != lock->end();) {
+ if (now > i->second->d_expires) {
+ i = lock->erase(i);
+ }
+ else {
+ ++i;
+ }
+ }
+}
+
+static void expire()
+{
+ static time_t s_last_expired;
+ time_t now = time(nullptr);
+ if (now - s_last_expired < 60) {
+ return;
+ }
+ s_last_expired = now;
+ doExpire(s_gss_init_creds, now);
+ doExpire(s_gss_accept_creds, now);
+ doExpire(s_gss_sec_context, now);
+}
bool GssContext::supported() { return true; }
{
d_label = label;
auto lock = s_gss_sec_context.lock();
- if (lock->find(d_label) != lock->end()) {
- d_ctx = (*lock)[d_label];
- d_type = d_ctx->d_type;
+ auto it = lock->find(d_label);
+ if (it != lock->end()) {
+ d_secctx = it->second;
+ d_type = d_secctx->d_type;
}
}
bool GssContext::expired()
{
- return (!d_ctx || (d_ctx->d_expires > -1 && d_ctx->d_expires < time(nullptr)));
+ return (!d_secctx || (d_secctx->d_expires > -1 && d_secctx->d_expires < time(nullptr)));
}
bool GssContext::valid()
{
- return (d_ctx && !expired() && d_ctx->d_state == GssSecContext::GssStateComplete);
+ return (d_secctx && !expired() && d_secctx->d_state == GssSecContext::GssStateComplete);
}
bool GssContext::init(const std::string& input, std::string& output)
{
+ expire();
+
OM_uint32 tmp_maj __attribute__((unused)), tmp_min __attribute__((unused));
OM_uint32 maj, min;
gss_buffer_desc recv_tok, send_tok, buffer;
OM_uint32 flags;
OM_uint32 expires;
- std::shared_ptr<GssCredential> cred;
if (d_label.empty()) {
d_error = GSS_CONTEXT_INVALID;
return false;
}
d_type = GSS_CONTEXT_INIT;
-
+ std::shared_ptr<GssCredential> cred;
{
auto lock = s_gss_init_creds.lock();
- if (lock->find(d_localPrincipal) != lock->end()) {
- cred = (*lock)[d_localPrincipal];
- }
- else {
- (*lock)[d_localPrincipal] = std::make_shared<GssCredential>(d_localPrincipal, GSS_C_INITIATE);
- cred = (*lock)[d_localPrincipal];
+ auto it = lock->find(d_localPrincipal);
+ if (it == lock->end()) {
+ it = lock->emplace(d_localPrincipal, std::make_shared<GssCredential>(d_localPrincipal, GSS_C_INITIATE)).first;
}
+ cred = it->second;
}
// see if we can find a context in non-completed state
- if (d_ctx) {
- if (d_ctx->d_state != GssSecContext::GssStateNegotiate) {
+ if (d_secctx) {
+ if (d_secctx->d_state != GssSecContext::GssStateNegotiate) {
d_error = GSS_CONTEXT_INVALID;
return false;
}
else {
// make context
auto lock = s_gss_sec_context.lock();
- (*lock)[d_label] = std::make_shared<GssSecContext>(cred);
- (*lock)[d_label]->d_type = d_type;
- d_ctx = (*lock)[d_label];
- d_ctx->d_state = GssSecContext::GssStateNegotiate;
+ d_secctx = std::make_shared<GssSecContext>(cred);
+ d_secctx->d_state = GssSecContext::GssStateNegotiate;
+ d_secctx->d_type = d_type;
+ (*lock)[d_label] = d_secctx;
}
recv_tok.length = input.size();
if (!d_peerPrincipal.empty()) {
buffer.value = const_cast<void*>(static_cast<const void*>(d_peerPrincipal.c_str()));
buffer.length = d_peerPrincipal.size();
- maj = gss_import_name(&min, &buffer, (gss_OID)GSS_KRB5_NT_PRINCIPAL_NAME, &(d_ctx->d_peer_name));
+ maj = gss_import_name(&min, &buffer, (gss_OID)GSS_KRB5_NT_PRINCIPAL_NAME, &(d_secctx->d_peer_name));
if (maj != GSS_S_COMPLETE) {
processError("gss_import_name", maj, min);
return false;
}
}
- maj = gss_init_sec_context(&min, cred->d_cred, &(d_ctx->d_ctx), d_ctx->d_peer_name, GSS_C_NO_OID, GSS_C_MUTUAL_FLAG | GSS_C_REPLAY_FLAG, GSS_C_INDEFINITE, GSS_C_NO_CHANNEL_BINDINGS, &recv_tok, nullptr, &send_tok, &flags, &expires);
+ maj = gss_init_sec_context(&min, cred->d_cred, &d_secctx->d_ctx, d_secctx->d_peer_name, GSS_C_NO_OID, GSS_C_MUTUAL_FLAG | GSS_C_REPLAY_FLAG, GSS_C_INDEFINITE, GSS_C_NO_CHANNEL_BINDINGS, &recv_tok, nullptr, &send_tok, &flags, &expires);
if (send_tok.length > 0) {
output.assign(static_cast<char*>(send_tok.value), send_tok.length);
}
if (maj == GSS_S_COMPLETE) {
- if (expires > GSS_C_INDEFINITE) {
- d_ctx->d_expires = time(nullptr) + expires;
+ // We do not want forever
+ if (expires == GSS_C_INDEFINITE) {
+ expires = 60;
}
- else {
- d_ctx->d_expires = -1;
- }
- d_ctx->d_state = GssSecContext::GssStateComplete;
+ d_secctx->d_expires = time(nullptr) + expires;
+ d_secctx->d_state = GssSecContext::GssStateComplete;
return true;
}
else if (maj != GSS_S_CONTINUE_NEEDED) {
bool GssContext::accept(const std::string& input, std::string& output)
{
+ expire();
+
OM_uint32 tmp_maj __attribute__((unused)), tmp_min __attribute__((unused));
OM_uint32 maj, min;
gss_buffer_desc recv_tok, send_tok;
OM_uint32 flags;
OM_uint32 expires;
- std::shared_ptr<GssCredential> cred;
if (d_label.empty()) {
d_error = GSS_CONTEXT_INVALID;
return false;
}
d_type = GSS_CONTEXT_ACCEPT;
+ std::shared_ptr<GssCredential> cred;
{
auto lock = s_gss_accept_creds.lock();
- if (lock->find(d_localPrincipal) != lock->end()) {
- cred = (*lock)[d_localPrincipal];
- }
- else {
- (*lock)[d_localPrincipal] = std::make_shared<GssCredential>(d_localPrincipal, GSS_C_ACCEPT);
- cred = (*lock)[d_localPrincipal];
+ auto it = lock->find(d_localPrincipal);
+ if (it == lock->end()) {
+ it = lock->emplace(d_localPrincipal, std::make_shared<GssCredential>(d_localPrincipal, GSS_C_ACCEPT)).first;
}
+ cred = it->second;
}
// see if we can find a context in non-completed state
- if (d_ctx) {
- if (d_ctx->d_state != GssSecContext::GssStateNegotiate) {
+ if (d_secctx) {
+ if (d_secctx->d_state != GssSecContext::GssStateNegotiate) {
d_error = GSS_CONTEXT_INVALID;
return false;
}
else {
// make context
auto lock = s_gss_sec_context.lock();
- (*lock)[d_label] = std::make_shared<GssSecContext>(cred);
- (*lock)[d_label]->d_type = d_type;
- d_ctx = (*lock)[d_label];
- d_ctx->d_state = GssSecContext::GssStateNegotiate;
+ d_secctx = std::make_shared<GssSecContext>(cred);
+ d_secctx->d_state = GssSecContext::GssStateNegotiate;
+ d_secctx->d_type = d_type;
+ (*lock)[d_label] = d_secctx;
}
recv_tok.length = input.size();
recv_tok.value = const_cast<void*>(static_cast<const void*>(input.c_str()));
- maj = gss_accept_sec_context(&min, &(d_ctx->d_ctx), cred->d_cred, &recv_tok, GSS_C_NO_CHANNEL_BINDINGS, &(d_ctx->d_peer_name), nullptr, &send_tok, &flags, &expires, nullptr);
+ maj = gss_accept_sec_context(&min, &d_secctx->d_ctx, cred->d_cred, &recv_tok, GSS_C_NO_CHANNEL_BINDINGS, &d_secctx->d_peer_name, nullptr, &send_tok, &flags, &expires, nullptr);
if (send_tok.length > 0) {
output.assign(static_cast<char*>(send_tok.value), send_tok.length);
}
if (maj == GSS_S_COMPLETE) {
- if (expires > GSS_C_INDEFINITE) {
- d_ctx->d_expires = time(nullptr) + expires;
- }
- else {
- d_ctx->d_expires = -1;
+ // We do not want forever
+ if (expires == GSS_C_INDEFINITE) {
+ expires = 60;
}
- d_ctx->d_state = GssSecContext::GssStateComplete;
+ d_secctx->d_expires = time(nullptr) + expires;
+ d_secctx->d_state = GssSecContext::GssStateComplete;
return true;
}
else if (maj != GSS_S_CONTINUE_NEEDED) {
recv_tok.length = input.size();
recv_tok.value = const_cast<void*>(static_cast<const void*>(input.c_str()));
- maj = gss_get_mic(&min, d_ctx->d_ctx, GSS_C_QOP_DEFAULT, &recv_tok, &send_tok);
+ maj = gss_get_mic(&min, d_secctx->d_ctx, GSS_C_QOP_DEFAULT, &recv_tok, &send_tok);
if (send_tok.length > 0) {
output.assign(static_cast<char*>(send_tok.value), send_tok.length);
gss_buffer_desc sign_tok = GSS_C_EMPTY_BUFFER;
recv_tok.length = input.size();
- recv_tok.value = const_cast<void*>(static_cast<const void*>(input.c_str()));
+ recv_tok.value = const_cast<void*>(static_cast<const void*>(input.c_str()));
sign_tok.length = signature.size();
- sign_tok.value = const_cast<void*>(static_cast<const void*>(signature.c_str()));
+ sign_tok.value = const_cast<void*>(static_cast<const void*>(signature.c_str()));
- maj = gss_verify_mic(&min, d_ctx->d_ctx, &recv_tok, &sign_tok, nullptr);
+ maj = gss_verify_mic(&min, d_secctx->d_ctx, &recv_tok, &sign_tok, nullptr);
if (maj != GSS_S_COMPLETE) {
processError("gss_get_mic", maj, min);
bool GssContext::destroy()
{
- return false;
+ if (d_label.empty()) {
+ return false;
+ }
+ auto lock = s_gss_sec_context.lock();
+ return lock->erase(d_label) == 1;
}
void GssContext::setLocalPrincipal(const std::string& name)
gss_buffer_desc value;
OM_uint32 maj, min;
- if (d_ctx->d_peer_name != GSS_C_NO_NAME) {
- maj = gss_display_name(&min, d_ctx->d_peer_name, &value, nullptr);
+ if (d_secctx->d_peer_name != GSS_C_NO_NAME) {
+ maj = gss_display_name(&min, d_secctx->d_peer_name, &value, nullptr);
if (maj == GSS_S_COMPLETE && value.length > 0) {
name.assign(static_cast<char*>(value.value), value.length);
maj = gss_release_buffer(&min, &value);
}
}
+std::tuple<size_t, size_t, size_t> GssContext::getCounts()
+{
+ return std::make_tuple(s_gss_init_creds.lock()->size(), s_gss_accept_creds.lock()->size(), s_gss_sec_context.lock()->size());
+}
+
void GssContext::processError(const std::string& method, OM_uint32 maj, OM_uint32 min)
{
OM_uint32 tmp_min;
ostringstream oss;
gss_display_status(&tmp_min, maj, GSS_C_GSS_CODE, GSS_C_NULL_OID, &msg_ctx, &msg);
oss << method << ": " << msg.value;
+ /// XXX leaks gss_buffer_desc?
d_gss_errors.push_back(oss.str());
if (!msg_ctx)
break;
ostringstream oss;
gss_display_status(&tmp_min, min, GSS_C_MECH_CODE, GSS_C_NULL_OID, &msg_ctx, &msg);
oss << method << ": " << msg.value;
+ /// XXX leaks gss_buffer_desc?
d_gss_errors.push_back(oss.str());
if (!msg_ctx)
break;