/** BEGIN COPYRIGHT BLOCK
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*
* Additional permission under GPLv3 section 7:
*
* In the following paragraph, "GPL" means the GNU General Public
* License, version 3 or any later version, and "Non-GPL Code" means
* code that is governed neither by the GPL nor a license
* compatible with the GPL.
*
* You may link the code of this Program with Non-GPL Code and convey
* linked combinations including the two, provided that such Non-GPL
* Code only links to the code of this Program through those well
* defined interfaces identified in the file named EXCEPTION found in
* the source code files (the "Approved Interfaces"). The files of
* Non-GPL Code may instantiate templates or use macros or inline
* functions from the Approved Interfaces without causing the resulting
* work to be covered by the GPL. Only the copyright holders of this
* Program may make changes or additions to the list of Approved
* Interfaces.
*
* Authors:
* Sumit Bose
*
* Copyright (C) 2017 Red Hat, Inc.
* All rights reserved.
* END COPYRIGHT BLOCK **/
#include
//#include
#include
#include
#include "ipa_krb5.h"
#include "ipa_kdb.h"
#define IPA_OC_CERTMAP_RULE "ipaCertMapRule"
#define IPA_CERTMAP_MAPRULE "ipaCertMapMapRule"
#define IPA_CERTMAP_MATCHRULE "ipaCertMapMatchRule"
#define IPA_CERTMAP_PRIORITY "ipaCertMapPriority"
#define IPA_ENABLED_FLAG "ipaEnabledFlag"
#define IPA_TRUE_VALUE "TRUE"
#define IPA_ASSOCIATED_DOMAIN "associatedDomain"
#define OBJECTCLASS "objectClass"
#define CERTMAP_FILTER "(&("OBJECTCLASS"="IPA_OC_CERTMAP_RULE")" \
"("IPA_ENABLED_FLAG"="IPA_TRUE_VALUE"))"
#define DEFAULT_CERTMAP_LIFETIME 300
#ifndef discard_const
#define discard_const(ptr) ((void *)((uintptr_t)(ptr)))
#endif
struct krb5_certauth_moddata_st {
char *local_domain;
struct sss_certmap_ctx *sss_certmap_ctx;
struct ipadb_context *ipactx;
time_t valid_until;
};
void ipa_certmap_debug(void *private,
const char *file, long line,
const char *function,
const char *format, ...)
{
va_list ap;
char str[255] = { 0 };
va_start(ap, format);
vsnprintf(str, sizeof(str)-1, format, ap);
va_end(ap);
krb5_klog_syslog(LOG_INFO, str);
}
void ipa_certauth_free_moddata(krb5_certauth_moddata *moddata)
{
if (moddata == NULL || *moddata == NULL) {
return;
}
free((*moddata)->local_domain);
(*moddata)->local_domain = NULL;
sss_certmap_free_ctx((*moddata)->sss_certmap_ctx);
(*moddata)->sss_certmap_ctx = NULL;
free(*moddata);
return;
}
static krb5_error_code ipa_get_init_data(krb5_context kcontext,
krb5_certauth_moddata moddata_out)
{
int ret;
struct sss_certmap_ctx *ctx = NULL;
struct ipadb_context *ipactx;
krb5_error_code kerr;
char *basedn = NULL;
LDAPMessage *result = NULL;
LDAPMessage *le;
LDAP *lc;
size_t c;
uint32_t prio;
char *map_rule = NULL;
char *match_rule = NULL;
char **domains = NULL;
const char *certmap_attrs[] = { OBJECTCLASS,
IPA_CERTMAP_PRIORITY,
IPA_CERTMAP_MATCHRULE,
IPA_CERTMAP_MAPRULE,
IPA_ASSOCIATED_DOMAIN,
IPA_ENABLED_FLAG,
NULL};
krb5_klog_syslog(LOG_INFO, "Initializing IPA certauth plugin.");
ipactx = ipadb_get_context(kcontext);
if (ipactx == NULL || ipactx->magic != IPA_CONTEXT_MAGIC) {
return KRB5_KDB_DBNOTINITED;
}
if (ipactx->certauth_moddata == NULL) {
ipactx->certauth_moddata = moddata_out;
if (ipactx->realm != NULL) {
ipactx->certauth_moddata->local_domain = strdup(ipactx->realm);
if (ipactx->certauth_moddata->local_domain == NULL) {
free(ipactx->certauth_moddata);
ipactx->certauth_moddata = NULL;
ret = ENOMEM;
goto done;
}
}
ipactx->certauth_moddata->ipactx = ipactx;
}
ret = asprintf(&basedn, "cn=certmap,%s", ipactx->base);
if (ret == -1) {
return ENOMEM;
}
kerr = ipadb_simple_search(ipactx,basedn, LDAP_SCOPE_SUBTREE,
CERTMAP_FILTER, discard_const(certmap_attrs),
&result);
if (kerr != 0 && kerr != KRB5_KDB_NOENTRY) {
goto done;
}
ret = sss_certmap_init(NULL, ipa_certmap_debug, NULL, &ctx);
if (ret != 0) {
return ret;
}
if (kerr == KRB5_KDB_NOENTRY) {
ret = sss_certmap_add_rule(ctx, SSS_CERTMAP_MIN_PRIO,
NULL, NULL, NULL);
if (ret != 0) {
goto done;
}
} else {
lc = ipactx->lcontext;
for (le = ldap_first_entry(lc, result); le;
le = ldap_next_entry(lc, le)) {
prio = SSS_CERTMAP_MIN_PRIO;
ret = ipadb_ldap_attr_to_uint32(lc, le, IPA_CERTMAP_PRIORITY,
&prio);
if (ret != 0 && ret != ENOENT) {
goto done;
}
free(map_rule);
map_rule = NULL;
ret = ipadb_ldap_attr_to_str(lc, le, IPA_CERTMAP_MAPRULE,
&map_rule);
if (ret != 0 && ret != ENOENT) {
goto done;
}
free(match_rule);
match_rule = NULL;
ret = ipadb_ldap_attr_to_str(lc, le, IPA_CERTMAP_MATCHRULE,
&match_rule);
if (ret != 0 && ret != ENOENT) {
goto done;
}
if (domains != NULL) {
for (c = 0; domains[c] != NULL; c++) {
free(domains[c]);
}
free(domains);
domains = NULL;
}
ret = ipadb_ldap_attr_to_strlist(lc, le, IPA_ASSOCIATED_DOMAIN,
&domains);
if (ret != 0 && ret != ENOENT) {
goto done;
}
ret = sss_certmap_add_rule(ctx, prio, match_rule, map_rule,
(const char **) domains);
if (ret != 0) {
goto done;
}
}
}
sss_certmap_free_ctx(ipactx->certauth_moddata->sss_certmap_ctx);
ipactx->certauth_moddata->sss_certmap_ctx = ctx;
ipactx->certauth_moddata->valid_until = time(NULL)
+ DEFAULT_CERTMAP_LIFETIME;
krb5_klog_syslog(LOG_DEBUG,
"Successfully updates certificate mapping rules.");
ret = 0;
done:
ldap_msgfree(result);
free(basedn);
free(map_rule);
free(match_rule);
if (domains != NULL) {
for (c = 0; domains[c] != NULL; c++) {
free(domains[c]);
}
free(domains);
domains = NULL;
}
if (ret != 0) {
sss_certmap_free_ctx(ctx);
}
return ret;
}
static krb5_error_code ipa_certauth_authorize(krb5_context context,
krb5_certauth_moddata moddata,
const uint8_t *cert,
size_t cert_len,
krb5_const_principal princ,
const void *opts,
const krb5_db_entry *db_entry,
char ***authinds_out)
{
char *cert_filter = NULL;
char **domains = NULL;
int ret;
size_t c;
char *principal = NULL;
char **auth_inds = NULL;
LDAPMessage *res = NULL;
krb5_error_code kerr;
LDAPMessage *lentry;
if (moddata == NULL) {
return KRB5_PLUGIN_NO_HANDLE;
}
if (moddata->sss_certmap_ctx == NULL || time(NULL) > moddata->valid_until) {
kerr = ipa_get_init_data(context, moddata);
if (kerr != 0) {
krb5_klog_syslog(LOG_ERR, "Failed to init certmapping data");
return KRB5_PLUGIN_NO_HANDLE;
}
}
ret = krb5_unparse_name(context, db_entry->princ, &principal);
if (ret != 0) {
ret = KRB5KDC_ERR_CERTIFICATE_MISMATCH;
goto done;
}
krb5_klog_syslog(LOG_INFO, "Doing certauth authorize for [%s]", principal);
ret = sss_certmap_get_search_filter(moddata->sss_certmap_ctx,
cert, cert_len,
&cert_filter, &domains);
if (ret != 0) {
if (ret == ENOENT) {
ret = KRB5KDC_ERR_CERTIFICATE_MISMATCH;
}
goto done;
}
krb5_klog_syslog(LOG_INFO, "Got cert filter [%s]", cert_filter);
/* If there are no domains assigned the rule will apply to the local
* domain only. */
if (domains != NULL) {
if (moddata->local_domain == NULL) {
/* We don't know our own domain name, in general this should not
* happen. But to be fault tolerant we allow matching rule which
* do not have a domain assigned. */
ret = KRB5KDC_ERR_CERTIFICATE_MISMATCH;
goto done;
}
for (c = 0; domains[c] != NULL; c++) {
if (strcasecmp(domains[c], moddata->local_domain) == 0) {
break;
}
}
/* Our domain was not in the list */
if (domains[c] == NULL) {
ret = KRB5KDC_ERR_CERTIFICATE_MISMATCH;
goto done;
}
}
kerr = ipadb_fetch_principals_with_extra_filter(moddata->ipactx,
KRB5_KDB_FLAG_ALIAS_OK,
principal,
cert_filter,
&res);
if (kerr != 0) {
krb5_klog_syslog(LOG_ERR, "Search failed [%d]", kerr);
ret = KRB5KDC_ERR_CERTIFICATE_MISMATCH;
goto done;
}
kerr = ipadb_find_principal(context, KRB5_KDB_FLAG_ALIAS_OK, res,
&principal, &lentry);
if (kerr == KRB5_KDB_NOENTRY) {
krb5_klog_syslog(LOG_INFO, "No matching entry found");
ret = KRB5KDC_ERR_CERTIFICATE_MISMATCH;
goto done;
} else if (kerr != 0) {
krb5_klog_syslog(LOG_ERR, "ipadb_find_principal failed [%d]", kerr);
ret = KRB5KDC_ERR_CERTIFICATE_MISMATCH;
goto done;
}
/* Associate authentication indicator "pkinit" with the successful match.
* SSSD interface doesn't give us a clue which rule did match
* so there is nothing more to add here. */
auth_inds = calloc(2, sizeof(char *));
if (auth_inds != NULL) {
ret = asprintf(&auth_inds[0], "pkinit");
if (ret != -1) {
auth_inds[1] = NULL;
*authinds_out = auth_inds;
} else {
free(auth_inds);
}
}
/* TODO: add more tests ? */
ret = 0;
done:
sss_certmap_free_filter_and_domains(cert_filter, domains);
krb5_free_unparsed_name(context, principal);
ldap_msgfree(res);
return ret;
}
static krb5_error_code ipa_certauth_init(krb5_context kcontext,
krb5_certauth_moddata *moddata_out)
{
struct krb5_certauth_moddata_st *certauth_moddata;
certauth_moddata = calloc(1, sizeof(struct krb5_certauth_moddata_st));
if (certauth_moddata == NULL) {
return ENOMEM;
}
*moddata_out = certauth_moddata;
return 0;
}
static void ipa_certauth_fini(krb5_context context,
krb5_certauth_moddata moddata_out)
{
krb5_klog_syslog(LOG_INFO, "IPA certauth plugin un-loaded.");
return;
}
static void ipa_certauth_free_indicator(krb5_context context,
krb5_certauth_moddata moddata,
char **authinds)
{
size_t i = 0;
if ((authinds == NULL) || (moddata == NULL)) {
return;
}
for(i=0; authinds[i]; i++) {
free(authinds[i]);
authinds[i] = NULL;
}
free(authinds);
}
krb5_error_code certauth_ipakdb_initvt(krb5_context context,
int maj_ver, int min_ver,
krb5_plugin_vtable vtable)
{
krb5_certauth_vtable vt;
if (maj_ver != 1) {
return KRB5_PLUGIN_VER_NOTSUPP;
}
vt = (krb5_certauth_vtable) vtable;
vt->name = "ipakdb";
vt->authorize = ipa_certauth_authorize;
vt->init = ipa_certauth_init;
vt->fini = ipa_certauth_fini;
vt->free_ind = ipa_certauth_free_indicator;
return 0;
}