freeipa/daemons/ipa-otpd/query.c
Alexander Bokovoy 3f6656e09a ipa-otpd: add support for SSSD OIDC helper
SSSD OIDC helper is used for negotiating with OAUTH2 or OIDC end points
of external identity providers (IdPs).

ipa-otpd daemon now is capable to take either Issuer URL or individual
endpoints and call SSSD OIDC helper accordingly.

Communication with SSSD OIDC helper can be debugged with the use of a
debug variable set in /etc/ipa/default.conf. Man page for
default.conf(5) has been updated to provide this information.

Fixes: https://pagure.io/freeipa/issue/8805

Signed-off-by: Sumit Bose <sbose@redhat.com>
Signed-off-by: Alexander Bokovoy <abokovoy@redhat.com>
Reviewed-By: Francisco Trivino <ftrivino@redhat.com>
Reviewed-By: Sumit Bose <sbose@redhat.com>
2022-05-10 15:52:41 +03:00

401 lines
12 KiB
C

/*
* FreeIPA 2FA companion daemon
*
* Authors: Nathaniel McCallum <npmccallum@redhat.com>
*
* Copyright (C) 2013 Nathaniel McCallum, Red Hat
* see file 'COPYING' for use and warranty information
*
* 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 <http://www.gnu.org/licenses/>.
*/
/*
* This file receives requests (from stdio.c) and queries the LDAP server for
* the user's configuration. When the user's configuration is received, it is
* parsed (parse.c). Once the configuration is parsed, the request packet is
* either forwarded to a third-party RADIUS server (forward.c) or authenticated
* directly via an LDAP bind (bind.c) based on the configuration received.
*/
#define _GNU_SOURCE 1 /* for asprintf() */
#include "internal.h"
#include <ctype.h>
#include <stdbool.h>
#define DEFAULT_TIMEOUT 15
#define DEFAULT_RETRIES 3
static char *user[] = {
"uid",
"ipatokenRadiusUserName",
"ipatokenRadiusConfigLink",
"ipaidpSub",
"ipaidpConfigLink",
"ipauserauthtype",
NULL
};
static char *radius[] = {
"ipatokenRadiusServer",
"ipatokenRadiusSecret",
"ipatokenRadiusTimeout",
"ipatokenRadiusRetries",
"ipatokenUserMapAttribute",
NULL
};
static char *idp[] = {
"ipaidpClientID",
"ipaidpClientSecret",
"ipaidpIssuerURL",
"ipaidpDevAuthEndpoint",
"ipaidpTokenEndpoint",
"ipaidpUserInfoEndpoint",
"ipaidpKeysEndpoint",
"ipaidpScope",
"ipaidpSub",
"cn",
NULL
};
static bool auth_type_is(char **auth_types, const char *check)
{
size_t c;
if (auth_types == NULL || check == NULL) {
return false;
}
for(c = 0; auth_types[c] != NULL; c++) {
if (strcasecmp(auth_types[c], check) == 0) {
return true;
}
}
return false;
}
/* Send queued LDAP requests to the server. */
static void on_query_writable(verto_ctx *vctx, verto_ev *ev)
{
struct otpd_queue *push = &ctx.stdio.responses;
const krb5_data *princ = NULL;
char *filter = NULL, *attrs[2];
int i = LDAP_SUCCESS;
struct otpd_queue_item *item;
(void)vctx;
item = otpd_queue_pop(&ctx.query.requests);
if (item == NULL) {
verto_set_flags(ctx.query.io, VERTO_EV_FLAG_PERSIST |
VERTO_EV_FLAG_IO_ERROR |
VERTO_EV_FLAG_IO_READ);
return;
}
if (item->user.dn == NULL) {
princ = krad_packet_get_attr(item->req,
krad_attr_name2num("User-Name"), 0);
if (princ == NULL)
goto error;
otpd_log_req(item->req, "user query start");
item->ldap_query = LDAP_QUERY_USER;
if (asprintf(&filter, "(&(objectClass=Person)(krbPrincipalName=%*s))",
princ->length, princ->data) < 0)
goto error;
i = ldap_search_ext(verto_get_private(ev), ctx.query.base,
LDAP_SCOPE_SUBTREE, filter, user, 0, NULL,
NULL, NULL, 1, &item->msgid);
free(filter);
} else if (auth_type_is(item->user.ipauserauthtypes, "idp")) {
otpd_log_req(item->req, "idp query start: %s",
item->user.ipaidpConfigLink);
item->ldap_query = LDAP_QUERY_IDP;
i = ldap_search_ext(verto_get_private(ev),
item->user.ipaidpConfigLink,
LDAP_SCOPE_BASE, NULL, idp, 0, NULL,
NULL, NULL, 1, &item->msgid);
} else if (item->radius.ipatokenRadiusSecret == NULL) {
otpd_log_req(item->req, "radius query start: %s",
item->user.ipatokenRadiusConfigLink);
item->ldap_query = LDAP_QUERY_RADIUS;
i = ldap_search_ext(verto_get_private(ev),
item->user.ipatokenRadiusConfigLink,
LDAP_SCOPE_BASE, NULL, radius, 0, NULL,
NULL, NULL, 1, &item->msgid);
} else if (item->radius.ipatokenUserMapAttribute != NULL) {
otpd_log_req(item->req, "username query start: %s",
item->radius.ipatokenUserMapAttribute);
item->ldap_query = LDAP_QUERY_RADIUS_USERMAP;
attrs[0] = item->radius.ipatokenUserMapAttribute;
attrs[1] = NULL;
i = ldap_search_ext(verto_get_private(ev), item->user.dn,
LDAP_SCOPE_BASE, NULL, attrs, 0, NULL,
NULL, NULL, 1, &item->msgid);
}
if (i == LDAP_SUCCESS) {
push = &ctx.query.responses;
}
error:
otpd_queue_push(push, item);
}
static enum oauth2_state get_oauth2_state(enum ldap_query ldap_query,
struct otpd_queue_item *item)
{
const krb5_data *data_pwd;
const krb5_data *data_state;
enum oauth2_state oauth2_state = OAUTH2_NO;
data_pwd = krad_packet_get_attr(item->req,
krad_attr_name2num("User-Password"), 0);
data_state = krad_packet_get_attr(item->req,
krad_attr_name2num("Proxy-State"), 0);
if (data_pwd == NULL && data_state == NULL) {
oauth2_state = OAUTH2_GET_DEVICE_CODE;
} else if (data_pwd == NULL && data_state != NULL) {
oauth2_state = OAUTH2_GET_ACCESS_TOKEN;
}
/* Looks like caller does not expect oauth2 authentication */
if (oauth2_state == OAUTH2_NO) {
return oauth2_state;
}
if (ldap_query == LDAP_QUERY_USER) {
/* Check the user entry for required attributes */
if (item->user.ipaidpSub == NULL) {
oauth2_state = OAUTH2_NO;
otpd_log_req(item->req,
"OAuth2 not possible, Missing 'sub' in user entry");
}
if (item->user.ipaidpConfigLink == NULL) {
oauth2_state = OAUTH2_NO;
otpd_log_req(item->req,
"OAuth2 not possible, Missing issuer in user entry");
}
if (oauth2_state != OAUTH2_NO) {
/* Next step is to lookup IdP data */
oauth2_state = OAUTH2_GET_ISSUER;
}
} else if (ldap_query == LDAP_QUERY_IDP) {
/* Check the idp entry for required attributes */
if (item->idp.ipaidpIssuerURL == NULL) {
if (item->idp.ipaidpDevAuthEndpoint == NULL) {
oauth2_state = OAUTH2_NO;
otpd_log_req(item->req,
"OAuth2 not possible, "
"Missing authentication end-point in idp entry");
}
if (item->idp.ipaidpTokenEndpoint == NULL) {
oauth2_state = OAUTH2_NO;
otpd_log_req(item->req,
"OAuth2 not possible, "
"Missing access token end-point in idp entry");
}
if (item->idp.ipaidpUserInfoEndpoint == NULL) {
oauth2_state = OAUTH2_NO;
otpd_log_req(item->req,
"OAuth2 not possible, "
"Missing userinfo end-point in idp entry");
}
}
if (item->idp.ipaidpClientID == NULL) {
oauth2_state = OAUTH2_NO;
otpd_log_req(item->req,
"OAuth2 not possible, Missing client ID in idp entry");
}
}
return oauth2_state;
}
/* Read LDAP responses from the server. */
static void on_query_readable(verto_ctx *vctx, verto_ev *ev)
{
struct otpd_queue *push = &ctx.stdio.responses;
verto_ev *event = ctx.stdio.writer;
LDAPMessage *results, *entry;
struct otpd_queue_item *item = NULL;
const char *err;
LDAP *ldp;
int i;
(void)vctx;
enum oauth2_state oauth2_state;
ldp = verto_get_private(ev);
i = ldap_result(ldp, LDAP_RES_ANY, 0, NULL, &results);
if (i != LDAP_RES_SEARCH_ENTRY && i != LDAP_RES_SEARCH_RESULT) {
if (i <= 0)
results = NULL;
ldap_msgfree(results);
otpd_log_err(EIO, "IO error received on query socket");
verto_break(ctx.vctx);
ctx.exitstatus = 1;
return;
}
item = otpd_queue_pop_msgid(&ctx.query.responses, ldap_msgid(results));
if (item == NULL)
goto egress;
if (i == LDAP_RES_SEARCH_ENTRY) {
entry = ldap_first_entry(ldp, results);
if (entry == NULL)
goto egress;
err = NULL;
switch (item->ldap_query) {
case LDAP_QUERY_USER:
err = otpd_parse_user(ldp, entry, item);
break;
case LDAP_QUERY_RADIUS:
err = otpd_parse_radius(ldp, entry, item);
break;
case LDAP_QUERY_RADIUS_USERMAP:
err = otpd_parse_radius_username(ldp, entry, item);
break;
case LDAP_QUERY_IDP:
err = otpd_parse_idp(ldp, entry, item);
break;
default:
ldap_msgfree(entry);
goto egress;
}
ldap_msgfree(entry);
if (err != NULL) {
if (item->error != NULL)
free(item->error);
item->error = strdup(err);
if (item->error == NULL)
goto egress;
}
otpd_queue_push_head(&ctx.query.responses, item);
return;
}
item->msgid = -1;
switch (item->ldap_query) {
case LDAP_QUERY_USER:
otpd_log_req(item->req, "user query end: %s",
item->error == NULL ? item->user.dn : item->error);
if (item->user.dn == NULL || item->user.uid == NULL)
goto egress;
break;
case LDAP_QUERY_RADIUS:
otpd_log_req(item->req, "radius query end: %s",
item->error == NULL
? item->radius.ipatokenRadiusServer
: item->error);
if (item->radius.ipatokenRadiusServer == NULL ||
item->radius.ipatokenRadiusSecret == NULL)
goto egress;
break;
case LDAP_QUERY_RADIUS_USERMAP:
otpd_log_req(item->req, "username query end: %s",
item->error == NULL ? item->user.other : item->error);
break;
case LDAP_QUERY_IDP:
otpd_log_req(item->req, "idp query end: %s",
item->error == NULL ? item->idp.name : item->error);
if (!item->idp.valid) {
goto egress;
}
break;
default:
goto egress;
}
/* Check for oauth2 */
oauth2_state = get_oauth2_state(item->ldap_query, item);
if (oauth2_state == OAUTH2_GET_ISSUER) {
push = &ctx.query.requests;
event = ctx.query.io;
goto egress;
} else if (oauth2_state != OAUTH2_NO) {
i = oauth2(&item, oauth2_state);
if (i != 0) {
goto egress;
} else {
/* oauth2 will call ctx.stdio.writer, so we can return here */
return;
}
}
if (item->error != NULL)
goto egress;
if (item->ldap_query == LDAP_QUERY_USER &&
item->user.ipatokenRadiusConfigLink != NULL) {
push = &ctx.query.requests;
event = ctx.query.io;
goto egress;
} else if (item->ldap_query == LDAP_QUERY_RADIUS &&
item->radius.ipatokenUserMapAttribute != NULL &&
item->user.ipatokenRadiusUserName == NULL) {
push = &ctx.query.requests;
event = ctx.query.io;
goto egress;
}
/* Forward to RADIUS if necessary. */
i = otpd_forward(&item);
if (i != 0)
goto egress;
push = &ctx.bind.requests;
event = ctx.bind.io;
egress:
ldap_msgfree(results);
otpd_queue_push(push, item);
if (item != NULL)
verto_set_flags(event, VERTO_EV_FLAG_PERSIST |
VERTO_EV_FLAG_IO_ERROR |
VERTO_EV_FLAG_IO_READ |
VERTO_EV_FLAG_IO_WRITE);
}
/* Handle the reading/writing of LDAP query requests asynchronously. */
void otpd_on_query_io(verto_ctx *vctx, verto_ev *ev)
{
verto_ev_flag flags;
flags = verto_get_fd_state(ev);
if (flags & VERTO_EV_FLAG_IO_WRITE)
on_query_writable(vctx, ev);
if (flags & (VERTO_EV_FLAG_IO_READ | VERTO_EV_FLAG_IO_ERROR))
on_query_readable(vctx, ev);
}