#include <freeradius-devel/ident.h>
RCSID("$Id$")
#include <freeradius-devel/radiusd.h>
#include <freeradius-devel/modules.h>
#include <krb5.h>
#include <com_err.h>
typedef struct rlm_krb5_t {
const char *keytab;
const char *service_princ;
krb5_context *context;
} rlm_krb5_t;
static const CONF_PARSER module_config[] = {
{ "keytab", PW_TYPE_STRING_PTR,
offsetof(rlm_krb5_t,keytab), NULL, NULL },
{ "service_principal", PW_TYPE_STRING_PTR,
offsetof(rlm_krb5_t,service_princ), NULL, NULL },
{ NULL, -1, 0, NULL, NULL }
};
#ifndef HEIMDAL_KRB5
static int verify_krb5_tgt(krb5_context context, rlm_krb5_t *instance,
const char *user, krb5_ccache ccache)
{
int r;
char phost[BUFSIZ];
krb5_principal princ;
krb5_keyblock *keyblock = 0;
krb5_data packet, *server;
krb5_auth_context auth_context = NULL;
krb5_keytab keytab;
char service[64] = "host";
char *servername = NULL;
if (instance->service_princ != NULL) {
servername = strchr(instance->service_princ, '/');
if (servername != NULL) {
*servername = '\0';
}
strlcpy(service,instance->service_princ,sizeof(service));
service[sizeof(service)-1] = '\0';
if (servername != NULL) {
*servername = '/';
servername++;
}
}
memset(&packet, 0, sizeof packet);
if ((r = krb5_sname_to_principal(context, servername, service,
KRB5_NT_SRV_HST, &princ)))
{
radlog(L_DBG, "rlm_krb5: [%s] krb5_sname_to_principal failed: %s",
user, error_message(r));
return RLM_MODULE_REJECT;
}
server = krb5_princ_component(c, princ, 1);
if (!server) {
radlog(L_DBG, "rlm_krb5: [%s] krb5_princ_component failed.",
user);
return RLM_MODULE_REJECT;
}
strlcpy(phost, server->data, BUFSIZ);
phost[BUFSIZ - 1] = '\0';
if ((r = krb5_kt_read_service_key(context, instance->keytab, princ, 0,
ENCTYPE_DES_CBC_MD5, &keyblock)))
{
radlog(L_DBG, "rlm_krb5: verify_krb_v5_tgt: host key not found : %s",
error_message(r));
return RLM_MODULE_OK;
}
if (keyblock)
krb5_free_keyblock(context, keyblock);
r = krb5_mk_req(context, &auth_context, 0, service, phost, NULL,
ccache, &packet);
if (auth_context) {
krb5_auth_con_free(context, auth_context);
auth_context = NULL;
}
if (r) {
radlog(L_DBG, "rlm_krb5: [%s] krb5_mk_req() failed: %s",
user, error_message(r));
r = RLM_MODULE_REJECT;
goto cleanup;
}
if (instance->keytab != NULL) {
r = krb5_kt_resolve(context, instance->keytab, &keytab);
}
if (instance->keytab == NULL || r) {
r = krb5_kt_default(context, &keytab);
}
if (r) {
radlog(L_AUTH, "rlm_krb5: [%s] krb5_kt_resolve failed: %s",
user, error_message(r));
r = RLM_MODULE_REJECT;
goto cleanup;
}
r = krb5_rd_req(context, &auth_context, &packet, princ,
keytab, NULL, NULL);
if (auth_context)
krb5_auth_con_free(context, auth_context);
krb5_kt_close(context, keytab);
if (r) {
radlog(L_AUTH, "rlm_krb5: [%s] krb5_rd_req() failed: %s",
user, error_message(r));
r = RLM_MODULE_REJECT;
} else {
r = RLM_MODULE_OK;
}
cleanup:
if (packet.data)
krb5_free_data_contents(context, &packet);
return r;
}
#endif
static int krb5_instantiate(CONF_SECTION *conf, void **instance)
{
int r;
rlm_krb5_t *data;
krb5_context *context;
data = rad_malloc(sizeof(*data));
memset(data, 0, sizeof(*data));
if (cf_section_parse(conf, data, module_config) < 0) {
free(data);
return -1;
}
context = data->context = rad_malloc(sizeof(*context));
if ((r = krb5_init_context(context)) ) {
radlog(L_AUTH, "rlm_krb5: krb5_init failed: %s",
error_message(r));
free(data);
return -1;
} else {
radlog(L_AUTH, "rlm_krb5: krb5_init ok");
}
*instance = data;
return 0;
}
static int krb5_detach(void *instance)
{
free(((rlm_krb5_t *)instance)->context);
free(instance);
return 0;
}
#ifndef HEIMDAL_KRB5
static int krb5_auth(void *instance, REQUEST *request)
{
int r;
krb5_data tgtname = {
0,
KRB5_TGS_NAME_SIZE,
KRB5_TGS_NAME
};
krb5_creds kcreds;
krb5_ccache ccache;
char cache_name[L_tmpnam + 8];
krb5_context context = *((rlm_krb5_t *)instance)->context;
const char *user, *pass;
if (!request->username) {
radlog(L_AUTH, "rlm_krb5: Attribute \"User-Name\" is required for authentication.");
return RLM_MODULE_INVALID;
}
if (!request->password) {
radlog(L_AUTH, "rlm_krb5: Attribute \"User-Password\" is required for authentication.");
return RLM_MODULE_INVALID;
}
if (request->password->attribute != PW_USER_PASSWORD) {
radlog(L_AUTH, "rlm_krb5: Attribute \"User-Password\" is required for authentication. Cannot use \"%s\".", request->password->name);
return RLM_MODULE_INVALID;
}
user = request->username->vp_strvalue;
pass = request->password->vp_strvalue;
memset(cache_name, 0, sizeof(cache_name));
strcpy(cache_name, "MEMORY:");
(void) tmpnam(&cache_name[7]);
if ((r = krb5_cc_resolve(context, cache_name, &ccache))) {
radlog(L_AUTH, "rlm_krb5: [%s] krb5_cc_resolve(): %s",
user, error_message(r));
return RLM_MODULE_REJECT;
}
memset((char *)&kcreds, 0, sizeof(kcreds));
if ( (r = krb5_parse_name(context, user, &kcreds.client)) ) {
radlog(L_AUTH, "rlm_krb5: [%s] krb5_parse_name failed: %s",
user, error_message(r));
return RLM_MODULE_REJECT;
}
if ((r = krb5_cc_initialize(context, ccache, kcreds.client))) {
radlog(L_AUTH, "rlm_krb5: [%s] krb5_cc_initialize(): %s",
user, error_message(r));
return RLM_MODULE_REJECT;
}
if ( (r = krb5_build_principal_ext(context, &kcreds.server,
krb5_princ_realm(context, kcreds.client)->length,
krb5_princ_realm(context, kcreds.client)->data,
tgtname.length,
tgtname.data,
krb5_princ_realm(context, kcreds.client)->length,
krb5_princ_realm(context, kcreds.client)->data,
0)) ) {
radlog(L_AUTH, "rlm_krb5: [%s] krb5_build_principal_ext failed: %s",
user, error_message(r));
krb5_cc_destroy(context, ccache);
return RLM_MODULE_REJECT;
}
if ( (r = krb5_get_in_tkt_with_password(context,
0, NULL, NULL, NULL, pass, ccache, &kcreds, 0)) ) {
radlog(L_AUTH, "rlm_krb5: [%s] krb5_g_i_t_w_p failed: %s",
user, error_message(r));
krb5_free_cred_contents(context, &kcreds);
krb5_cc_destroy(context, ccache);
return RLM_MODULE_REJECT;
} else {
r = verify_krb5_tgt(context, (rlm_krb5_t *)instance, user, ccache);
krb5_free_cred_contents(context, &kcreds);
krb5_cc_destroy(context, ccache);
return r;
}
return RLM_MODULE_REJECT;
}
#else
static int krb5_auth(void *instance, REQUEST *request)
{
int r;
krb5_error_code ret;
krb5_ccache id;
krb5_principal userP;
krb5_context context = *((rlm_krb5_t *)instance)->context;
const char *user, *pass;
if (!request->username) {
radlog(L_AUTH, "rlm_krb5: Attribute \"User-Name\" is required for authentication.");
return RLM_MODULE_INVALID;
}
if (!request->password) {
radlog(L_AUTH, "rlm_krb5: Attribute \"User-Password\" is required for authentication.");
return RLM_MODULE_INVALID;
}
if (request->password->attribute != PW_USER_PASSWORD) {
radlog(L_AUTH, "rlm_krb5: Attribute \"User-Password\" is required for authentication. Cannot use \"%s\".", request->password->name);
return RLM_MODULE_INVALID;
}
user = request->username->vp_strvalue;
pass = request->password->vp_strvalue;
if ( (r = krb5_parse_name(context, user, &userP)) ) {
radlog(L_AUTH, "rlm_krb5: [%s] krb5_parse_name failed: %s",
user, error_message(r));
return RLM_MODULE_REJECT;
}
radlog(L_AUTH, "rlm_krb5: Parsed name is: %s@%s\n",
*userP->name.name_string.val,
userP->realm);
krb5_cc_default(context, &id);
ret = krb5_verify_user(context,
userP,
id,
pass, 1, "radius");
if (ret == 0)
return RLM_MODULE_OK;
radlog(L_AUTH, "rlm_krb5: failed verify_user: %s (%s@%s )",
error_message(ret),
*userP->name.name_string.val,
userP->realm);
return RLM_MODULE_REJECT;
}
#endif
module_t rlm_krb5 = {
RLM_MODULE_INIT,
"Kerberos",
RLM_TYPE_THREAD_UNSAFE,
krb5_instantiate,
krb5_detach,
{
krb5_auth,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL
},
};