mirror of
https://salsa.debian.org/freeipa-team/freeipa.git
synced 2025-02-25 18:55:28 -06:00
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>
This commit is contained in:
parent
03a905eed9
commit
3f6656e09a
@ -160,6 +160,9 @@ Specifies the mode the server is running in. The currently support values are \f
|
||||
.B mount_ipa <URI>
|
||||
Specifies the mount point that the development server will register. The default is /ipa/
|
||||
.TP
|
||||
.B oidc_child_debug_level <debuglevel>
|
||||
Specifies the debug level of \fBoidc_child\fR, a helper process used by \fBipa-otpd\fR for OIDC/OAuth2 authentication. Level can be between 0 and 10, the higher the more details. If the level is 6 or higher HTTP debug output is added as well.
|
||||
.TP
|
||||
.B prompt_all <boolean>
|
||||
Specifies that all options should be prompted for in the IPA client, even optional values. Default is False.
|
||||
.TP
|
||||
|
@ -9,7 +9,8 @@ ipa_otpd_LDADD = $(top_builddir)/util/libutil.la
|
||||
dist_noinst_DATA = ipa-otpd.socket.in ipa-otpd@.service.in test.py
|
||||
systemdsystemunit_DATA = ipa-otpd.socket ipa-otpd@.service
|
||||
|
||||
ipa_otpd_SOURCES = bind.c forward.c main.c parse.c query.c queue.c stdio.c
|
||||
ipa_otpd_SOURCES = bind.c forward.c main.c parse.c query.c queue.c stdio.c \
|
||||
oauth2.c
|
||||
|
||||
%.socket: %.socket.in
|
||||
@sed -e 's|@krb5rundir[@]|$(krb5rundir)|g' \
|
||||
|
@ -28,6 +28,13 @@
|
||||
|
||||
#include <errno.h>
|
||||
|
||||
#ifndef UCHAR_MAX
|
||||
#define UCHAR_MAX 255
|
||||
#endif
|
||||
|
||||
/* RFC 2865 */
|
||||
#define MAX_ATTRSIZE (UCHAR_MAX - 2)
|
||||
|
||||
#define SECRET ""
|
||||
#define otpd_log_req(req, ...) \
|
||||
otpd_log_req_(__FILE__, __LINE__, (req), __VA_ARGS__)
|
||||
@ -36,11 +43,28 @@
|
||||
|
||||
struct otpd_queue_iter;
|
||||
|
||||
enum ldap_query {
|
||||
LDAP_QUERY_EMPTY = 0,
|
||||
LDAP_QUERY_USER,
|
||||
LDAP_QUERY_RADIUS,
|
||||
LDAP_QUERY_RADIUS_USERMAP,
|
||||
LDAP_QUERY_IDP,
|
||||
LDAP_QUERY_END
|
||||
};
|
||||
|
||||
enum oauth2_state {
|
||||
OAUTH2_NO = 0,
|
||||
OAUTH2_GET_ISSUER,
|
||||
OAUTH2_GET_DEVICE_CODE,
|
||||
OAUTH2_GET_ACCESS_TOKEN
|
||||
};
|
||||
|
||||
struct otpd_queue_item {
|
||||
struct otpd_queue_item *next;
|
||||
krad_packet *req;
|
||||
krad_packet *rsp;
|
||||
size_t sent;
|
||||
enum ldap_query ldap_query;
|
||||
char *error;
|
||||
|
||||
struct {
|
||||
@ -48,6 +72,9 @@ struct otpd_queue_item {
|
||||
char *uid;
|
||||
char *ipatokenRadiusUserName;
|
||||
char *ipatokenRadiusConfigLink;
|
||||
char *ipaidpSub;
|
||||
char *ipaidpConfigLink;
|
||||
char **ipauserauthtypes;
|
||||
char *other;
|
||||
} user;
|
||||
|
||||
@ -58,6 +85,28 @@ struct otpd_queue_item {
|
||||
time_t ipatokenRadiusTimeout;
|
||||
size_t ipatokenRadiusRetries;
|
||||
} radius;
|
||||
|
||||
struct {
|
||||
char *name;
|
||||
char *ipaidpIssuerURL;
|
||||
char *ipaidpDevAuthEndpoint;
|
||||
char *ipaidpTokenEndpoint;
|
||||
char *ipaidpUserInfoEndpoint;
|
||||
char *ipaidpKeysEndpoint;
|
||||
char *ipaidpClientID;
|
||||
char *ipaidpClientSecret;
|
||||
char *ipaidpScope;
|
||||
char *ipaidpSub;
|
||||
krb5_boolean valid;
|
||||
char* ipaidpDebugLevelStr;
|
||||
krb5_boolean ipaidpDebugCurl;
|
||||
} idp;
|
||||
|
||||
struct {
|
||||
char *device_code_reply;
|
||||
krb5_data state;
|
||||
} oauth2;
|
||||
|
||||
int msgid;
|
||||
};
|
||||
|
||||
@ -98,6 +147,10 @@ struct otpd_context {
|
||||
struct otpd_queue requests;
|
||||
struct otpd_queue responses;
|
||||
} bind;
|
||||
|
||||
struct {
|
||||
struct otpd_queue states;
|
||||
} oauth2_state;
|
||||
};
|
||||
|
||||
extern struct otpd_context ctx;
|
||||
@ -143,8 +196,13 @@ krb5_error_code otpd_forward(struct otpd_queue_item **i);
|
||||
const char *otpd_parse_user(LDAP *ldp, LDAPMessage *entry,
|
||||
struct otpd_queue_item *item);
|
||||
|
||||
const char *otpd_parse_idp(LDAP *ldp, LDAPMessage *entry,
|
||||
struct otpd_queue_item *item);
|
||||
|
||||
const char *otpd_parse_radius(LDAP *ldp, LDAPMessage *entry,
|
||||
struct otpd_queue_item *item);
|
||||
|
||||
const char *otpd_parse_radius_username(LDAP *ldp, LDAPMessage *entry,
|
||||
struct otpd_queue_item *item);
|
||||
|
||||
int oauth2(struct otpd_queue_item **item, enum oauth2_state);
|
||||
|
@ -242,7 +242,8 @@ int main(int argc, char **argv)
|
||||
goto error;
|
||||
}
|
||||
|
||||
ctx.vctx = verto_new(NULL, VERTO_EV_TYPE_IO | VERTO_EV_TYPE_SIGNAL);
|
||||
ctx.vctx = verto_default(NULL, VERTO_EV_TYPE_IO | VERTO_EV_TYPE_SIGNAL
|
||||
| VERTO_EV_TYPE_CHILD);
|
||||
if (ctx.vctx == NULL) {
|
||||
otpd_log_err(ENOMEM, "Unable to initialize event loop");
|
||||
goto error;
|
||||
@ -342,4 +343,3 @@ error:
|
||||
krb5_free_context(ctx.kctx);
|
||||
return ctx.exitstatus;
|
||||
}
|
||||
|
||||
|
614
daemons/ipa-otpd/oauth2.c
Normal file
614
daemons/ipa-otpd/oauth2.c
Normal file
@ -0,0 +1,614 @@
|
||||
/*
|
||||
* FreeIPA 2FA companion daemon
|
||||
*
|
||||
* Authors: Sumit Bose <sbose@redhat.com>
|
||||
*
|
||||
* Copyright (C) 2021 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 reaches out to a third-party IdP to handle an OAuth2
|
||||
* authentication request (stdio.c/query.c) if the user is configured
|
||||
* accordingly. The result is placed in the stdout queue (stdio.c).
|
||||
*/
|
||||
|
||||
#include <krb5/krb5.h>
|
||||
#include <stdbool.h>
|
||||
#include <unistd.h>
|
||||
#include <fcntl.h>
|
||||
#include <sys/random.h>
|
||||
|
||||
#include "internal.h"
|
||||
|
||||
#define OIDC_CHILD_PATH "/usr/libexec/sssd/oidc_child"
|
||||
|
||||
struct child_ctx {
|
||||
int read_from_child;
|
||||
int write_to_child;
|
||||
verto_ev *read_ev;
|
||||
verto_ev *write_ev;
|
||||
verto_ev *child_ev;
|
||||
struct otpd_queue_item *item;
|
||||
struct otpd_queue_item *saved_item;
|
||||
enum oauth2_state oauth2_state;
|
||||
};
|
||||
|
||||
static int set_fd_nonblocking(int fd)
|
||||
{
|
||||
int flags;
|
||||
int ret;
|
||||
|
||||
flags = fcntl(fd, F_GETFL, 0);
|
||||
if (flags == -1) {
|
||||
ret = errno;
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (fcntl(fd, F_SETFL, flags | O_NONBLOCK) == -1) {
|
||||
ret = errno;
|
||||
return ret;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void free_child_ctx(verto_ctx *vctx, verto_ev *ev)
|
||||
{
|
||||
(void)vctx; /* Unused */
|
||||
struct child_ctx *child_ctx;
|
||||
|
||||
child_ctx = verto_get_private(ev);
|
||||
|
||||
free(child_ctx);
|
||||
}
|
||||
|
||||
static void oauth2_on_child_exit(verto_ctx *vctx, verto_ev *ev)
|
||||
{
|
||||
(void)vctx; /* Unused */
|
||||
verto_proc_status st;
|
||||
|
||||
st = verto_get_proc_status(ev);
|
||||
|
||||
/* The krad req might not be available at this stage anymore, so
|
||||
* otpd_log_err() is used. */
|
||||
otpd_log_err(0, "Child finished with status [%d].", WEXITSTATUS(st));
|
||||
}
|
||||
|
||||
static void oauth2_on_child_writable(verto_ctx *vctx, verto_ev *ev)
|
||||
{
|
||||
(void)vctx; /* Unused */
|
||||
ssize_t io;
|
||||
struct child_ctx *child_ctx;
|
||||
|
||||
child_ctx = verto_get_private(ev);
|
||||
if (child_ctx == NULL) {
|
||||
otpd_log_err(EINVAL, "Lost child context");
|
||||
verto_del(ev);
|
||||
return;
|
||||
}
|
||||
|
||||
if (child_ctx->oauth2_state == OAUTH2_GET_DEVICE_CODE) {
|
||||
/* no input needed */
|
||||
verto_del(ev);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
io = write(verto_get_fd(ev),
|
||||
child_ctx->saved_item->oauth2.device_code_reply,
|
||||
strlen(child_ctx->saved_item->oauth2.device_code_reply));
|
||||
otpd_queue_item_free(child_ctx->saved_item);
|
||||
|
||||
if (io < 0) {
|
||||
switch (errno) {
|
||||
#if defined(EWOULDBLOCK) && (!defined(EAGAIN) || EAGAIN - EWOULDBLOCK != 0)
|
||||
case EWOULDBLOCK:
|
||||
#endif
|
||||
#if defined(EAGAIN)
|
||||
case EAGAIN:
|
||||
#endif
|
||||
case ENOBUFS:
|
||||
case EINTR:
|
||||
/* In this case, we just need to try again. */
|
||||
return;
|
||||
default:
|
||||
/* Unrecoverable. */
|
||||
break;
|
||||
}
|
||||
otpd_log_err(errno, "Failed to send to child");
|
||||
}
|
||||
|
||||
verto_del(ev);
|
||||
}
|
||||
|
||||
#define min(a,b) ((a) > (b) ? (b) : (a))
|
||||
static int add_krad_attr_to_set(struct child_ctx *child_ctx,
|
||||
krad_attrset *attrset,
|
||||
krb5_data *datap,
|
||||
krad_attr attr, const char *message)
|
||||
{
|
||||
krb5_data state = {0};
|
||||
char *p = datap->data;
|
||||
unsigned int len = datap->length;
|
||||
int ret = 0;
|
||||
|
||||
do {
|
||||
state.data = p;
|
||||
state.length = min(MAX_ATTRSIZE - 5, len);
|
||||
p += state.length;
|
||||
|
||||
ret = krad_attrset_add(attrset, attr, &(state));
|
||||
if (ret != 0) {
|
||||
otpd_log_req(child_ctx->item->req, message);
|
||||
break;
|
||||
}
|
||||
len -= state.length;
|
||||
} while (len > 0);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* Most attributes have limited length (MAX_ATTRSIZE). In order to accept longer
|
||||
* values, we will concatenate all the attribute values to single krb5_data. */
|
||||
static int get_krad_attr_from_packet(const krad_packet *rres,
|
||||
krad_attr attr, krb5_data *_data)
|
||||
{
|
||||
const krb5_data *rmsg;
|
||||
krb5_data data = {0};
|
||||
unsigned int memindex;
|
||||
unsigned int i;
|
||||
|
||||
i = 0;
|
||||
do {
|
||||
rmsg = krad_packet_get_attr(rres, attr, i);
|
||||
if (rmsg != NULL) {
|
||||
data.length += rmsg->length;
|
||||
}
|
||||
i++;
|
||||
} while (rmsg != NULL);
|
||||
|
||||
if (data.length == 0) {
|
||||
return ENOENT;
|
||||
}
|
||||
|
||||
data.data = malloc(data.length);
|
||||
if (data.data == NULL) {
|
||||
return ENOMEM;
|
||||
}
|
||||
|
||||
i = 0;
|
||||
memindex = 0;
|
||||
do {
|
||||
rmsg = krad_packet_get_attr(rres, attr, i);
|
||||
if (rmsg != NULL) {
|
||||
memcpy(&data.data[memindex], rmsg->data, rmsg->length);
|
||||
memindex += rmsg->length;
|
||||
}
|
||||
i++;
|
||||
} while (rmsg != NULL);
|
||||
|
||||
if (memindex != data.length) {
|
||||
free(data.data);
|
||||
return ERANGE;
|
||||
}
|
||||
|
||||
*_data = data;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* oidc_child will return two lines.
|
||||
* The first is a JSON formatted string containing the device code and other
|
||||
* data needed to get the access token in the second round. This will be
|
||||
* returned to the caller as Radius Proxy-State so that the caller will send
|
||||
* it back in the next round.
|
||||
* The second line is the string expected by the krb5 oauth2 pre-auth plugin
|
||||
* and will be send to the caller as Radius Reply-Message.
|
||||
*/
|
||||
static int handle_device_code_reply(struct child_ctx *child_ctx,
|
||||
const char *dc_reply, char *rad_reply)
|
||||
{
|
||||
krad_attrset *attrset = NULL;
|
||||
int ret;
|
||||
krb5_data data = { 0 };
|
||||
struct otpd_queue_item *state_item;
|
||||
|
||||
ret = otpd_queue_item_new(NULL, &state_item);
|
||||
if (ret != 0) {
|
||||
otpd_log_req(child_ctx->item->req, "Failed to allocate state item");
|
||||
goto done;
|
||||
}
|
||||
|
||||
state_item->oauth2.device_code_reply = strdup(dc_reply);
|
||||
if (state_item->oauth2.device_code_reply == NULL) {
|
||||
otpd_log_req(child_ctx->item->req, "Failed to copy device code reply.");
|
||||
goto done;
|
||||
}
|
||||
|
||||
ret = krad_attrset_new(ctx.kctx, &attrset);
|
||||
if (ret != 0) {
|
||||
otpd_log_req(child_ctx->item->req,
|
||||
"Failed to create radius attribute set");
|
||||
goto done;
|
||||
}
|
||||
|
||||
state_item->oauth2.state.magic = 0;
|
||||
|
||||
state_item->oauth2.state.data = strdup(dc_reply);
|
||||
if (state_item->oauth2.state.data == NULL) {
|
||||
otpd_log_req(child_ctx->item->req,
|
||||
"Failed to copy device code reply to krad.");
|
||||
goto done;
|
||||
}
|
||||
state_item->oauth2.state.length = strlen(dc_reply);
|
||||
|
||||
ret = add_krad_attr_to_set(child_ctx, attrset, &(state_item->oauth2.state),
|
||||
krad_attr_name2num("Proxy-State"),
|
||||
"Failed to serialize state to attribute set");
|
||||
if (ret != 0) {
|
||||
goto done;
|
||||
}
|
||||
|
||||
data.magic = 0;
|
||||
data.data = rad_reply;
|
||||
data.length = strlen(rad_reply);
|
||||
ret = add_krad_attr_to_set(child_ctx, attrset, &data,
|
||||
krad_attr_name2num("Reply-Message"),
|
||||
"Failed to serialize reply to attribute set");
|
||||
if (ret != 0) {
|
||||
goto done;
|
||||
}
|
||||
|
||||
ret = krad_packet_new_response(ctx.kctx, SECRET,
|
||||
krad_code_name2num("Access-Challenge"),
|
||||
attrset,
|
||||
child_ctx->item->req, &child_ctx->item->rsp);
|
||||
if (ret != 0) {
|
||||
otpd_log_err(ret, "Failed to create radius response");
|
||||
child_ctx->item->rsp = NULL;
|
||||
}
|
||||
|
||||
otpd_queue_push(&ctx.oauth2_state.states, state_item);
|
||||
|
||||
ret = 0;
|
||||
done:
|
||||
krad_attrset_free(attrset);
|
||||
if (ret != 0) {
|
||||
if (state_item != NULL) {
|
||||
free(state_item->oauth2.state.data);
|
||||
free(state_item->oauth2.device_code_reply);
|
||||
free(state_item);
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int check_access_token_reply(struct child_ctx *child_ctx,
|
||||
const char *buf, size_t len)
|
||||
{
|
||||
int ret;
|
||||
|
||||
if (strlen(child_ctx->item->user.ipaidpSub) != len
|
||||
|| memcmp(child_ctx->item->user.ipaidpSub, buf, len) != 0) {
|
||||
return EPERM;
|
||||
}
|
||||
|
||||
ret = krad_packet_new_response(ctx.kctx, SECRET,
|
||||
krad_code_name2num("Access-Accept"), NULL,
|
||||
child_ctx->item->req, &child_ctx->item->rsp);
|
||||
if (ret != 0) {
|
||||
otpd_log_err(ret, "Failed to create radius response");
|
||||
child_ctx->item->rsp = NULL;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void oauth2_on_child_readable(verto_ctx *, verto_ev *ev)
|
||||
{
|
||||
static char buf[10240];
|
||||
ssize_t io = 0;
|
||||
struct child_ctx *child_ctx = NULL;
|
||||
int ret;
|
||||
char *rad_reply;
|
||||
char *end;
|
||||
|
||||
child_ctx = (struct child_ctx *) verto_get_private(ev);
|
||||
if (child_ctx == NULL) {
|
||||
otpd_log_err(EINVAL, "Lost child context");
|
||||
verto_del(ev);
|
||||
return;
|
||||
}
|
||||
/* Make sure ctx.stdio.responses will at least return an error */
|
||||
child_ctx->item->rsp = NULL;
|
||||
child_ctx->item->sent = 0;
|
||||
|
||||
io = read(verto_get_fd(ev), buf, 10240);
|
||||
if (io < 0) {
|
||||
otpd_log_err(errno, "Failed to read from child");
|
||||
goto done;
|
||||
}
|
||||
|
||||
if (io >= 0) {
|
||||
buf[io] = '\0';
|
||||
otpd_log_req(child_ctx->item->req, "Received: [%s]", buf);
|
||||
}
|
||||
|
||||
verto_del(ev);
|
||||
|
||||
if (child_ctx->oauth2_state == OAUTH2_GET_DEVICE_CODE) {
|
||||
/* expect 2 lines of output. First the orginal JSON string return by
|
||||
* the IdP from the devicecode request which will be used as input to
|
||||
* the child process in the second run. Second the JSON string returned
|
||||
* in the radius reply. */
|
||||
|
||||
rad_reply = memchr(buf, '\n', io);
|
||||
if (rad_reply != NULL) {
|
||||
*rad_reply = '\0';
|
||||
rad_reply++;
|
||||
end = memchr(rad_reply, '\n', io - (rad_reply - 1 - buf));
|
||||
if (end == NULL) {
|
||||
otpd_log_req(child_ctx->item->req, "Missing second new-line.");
|
||||
goto done;
|
||||
}
|
||||
*end = '\0';
|
||||
|
||||
ret = handle_device_code_reply(child_ctx, buf, rad_reply);
|
||||
if (ret != 0) {
|
||||
otpd_log_req(child_ctx->item->req,
|
||||
"Failed to handle device code reply.");
|
||||
}
|
||||
}
|
||||
} else if (child_ctx->oauth2_state == OAUTH2_GET_ACCESS_TOKEN) {
|
||||
ret = check_access_token_reply(child_ctx, buf, (size_t) io);
|
||||
if (ret != 0) {
|
||||
otpd_log_req(child_ctx->item->req,
|
||||
"Failed to check access token reply.");
|
||||
}
|
||||
} else {
|
||||
/* error */
|
||||
otpd_log_req(child_ctx->item->req, "Unexpected state [%d].",
|
||||
child_ctx->oauth2_state);
|
||||
}
|
||||
|
||||
done:
|
||||
otpd_queue_push(&ctx.stdio.responses, child_ctx->item);
|
||||
verto_set_flags(ctx.stdio.writer, VERTO_EV_FLAG_PERSIST |
|
||||
VERTO_EV_FLAG_IO_ERROR |
|
||||
VERTO_EV_FLAG_IO_READ |
|
||||
VERTO_EV_FLAG_IO_WRITE);
|
||||
}
|
||||
|
||||
static const char *oauth2_state_to_str(enum oauth2_state oauth2_state)
|
||||
{
|
||||
switch (oauth2_state) {
|
||||
case OAUTH2_NO:
|
||||
return "OAuth2 not available";
|
||||
break;
|
||||
case OAUTH2_GET_ISSUER:
|
||||
return "Get issuer from LDAP";
|
||||
break;
|
||||
case OAUTH2_GET_DEVICE_CODE:
|
||||
return "Get device code";
|
||||
break;
|
||||
case OAUTH2_GET_ACCESS_TOKEN:
|
||||
return "Get access token";
|
||||
break;
|
||||
default:
|
||||
return "Unknown OAuth2 state";
|
||||
}
|
||||
}
|
||||
|
||||
int oauth2(struct otpd_queue_item **item, enum oauth2_state oauth2_state)
|
||||
{
|
||||
int ret;
|
||||
pid_t child_pid;
|
||||
int pipefd_to_child[2] = { -1, -1};
|
||||
int pipefd_from_child[2] = { -1, -1};
|
||||
struct child_ctx *child_ctx;
|
||||
/* Up to 50 arguments to the helper supported. The amount of arguments
|
||||
* is controlled inside this function. Right now max used is below 20 */
|
||||
char *args[50] = {NULL};
|
||||
size_t args_idx = 0;
|
||||
krb5_data data_state = {0};
|
||||
struct otpd_queue_item *saved_item;
|
||||
|
||||
if (oauth2_state != OAUTH2_GET_DEVICE_CODE
|
||||
&& oauth2_state != OAUTH2_GET_ACCESS_TOKEN) {
|
||||
otpd_log_req((*item)->req, "Unexpected OAuth2 state [%d][%s]",
|
||||
oauth2_state, oauth2_state_to_str(oauth2_state));
|
||||
return EINVAL;
|
||||
}
|
||||
|
||||
if (oauth2_state == OAUTH2_GET_ACCESS_TOKEN) {
|
||||
ret = get_krad_attr_from_packet((*item)->req,
|
||||
krad_attr_name2num("Proxy-State"),
|
||||
&data_state);
|
||||
if ((ret != 0) || (data_state.length == 0)) {
|
||||
otpd_log_req((*item)->req, "Missing Radius Proxy-State attribute");
|
||||
return EINVAL;
|
||||
}
|
||||
|
||||
saved_item = calloc(sizeof(struct otpd_queue_item), 1);
|
||||
if (saved_item == NULL) {
|
||||
otpd_log_req((*item)->req, "No matching saved state found");
|
||||
return EINVAL;
|
||||
}
|
||||
saved_item->oauth2.device_code_reply = strndup(data_state.data,
|
||||
data_state.length);
|
||||
if (saved_item->oauth2.device_code_reply == NULL) {
|
||||
otpd_log_req((*item)->req, "Failed to copy device code reply");
|
||||
return EINVAL;
|
||||
}
|
||||
krb5_free_data_contents(NULL, &data_state);
|
||||
}
|
||||
|
||||
child_ctx = calloc(sizeof(struct child_ctx), 1);
|
||||
if (child_ctx == NULL) {
|
||||
ret = ENOMEM;
|
||||
goto done;
|
||||
}
|
||||
child_ctx->item = (*item);
|
||||
child_ctx->saved_item = saved_item;
|
||||
child_ctx->oauth2_state = oauth2_state;
|
||||
|
||||
otpd_log_req((*item)->req, "oauth2 start: %s",
|
||||
oauth2_state_to_str(oauth2_state));
|
||||
|
||||
args[args_idx++] = OIDC_CHILD_PATH;
|
||||
|
||||
if (oauth2_state == OAUTH2_GET_DEVICE_CODE) {
|
||||
args[args_idx++] = "--get-device-code";
|
||||
} else {
|
||||
args[args_idx++] = "--get-access-token";
|
||||
}
|
||||
|
||||
if ((*item)->idp.ipaidpIssuerURL != NULL) {
|
||||
args[args_idx++] = "--issuer-url";
|
||||
args[args_idx++] = (*item)->idp.ipaidpIssuerURL;
|
||||
} else {
|
||||
args[args_idx++] = "--device-auth-endpoint";
|
||||
args[args_idx++] = (*item)->idp.ipaidpDevAuthEndpoint;
|
||||
|
||||
args[args_idx++] = "--token-endpoint";
|
||||
args[args_idx++] = (*item)->idp.ipaidpTokenEndpoint;
|
||||
|
||||
args[args_idx++] = "--userinfo-endpoint";
|
||||
args[args_idx++] = (*item)->idp.ipaidpUserInfoEndpoint;
|
||||
|
||||
if ((*item)->idp.ipaidpKeysEndpoint) {
|
||||
args[args_idx++] = "--jwks-uri";
|
||||
args[args_idx++] = (*item)->idp.ipaidpKeysEndpoint;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
args[args_idx++] = "--client-id";
|
||||
args[args_idx++] = (*item)->idp.ipaidpClientID;
|
||||
|
||||
if ((*item)->idp.ipaidpClientSecret) {
|
||||
args[args_idx++] = "--client-secret";
|
||||
args[args_idx++] = (*item)->idp.ipaidpClientSecret;
|
||||
}
|
||||
|
||||
if ((*item)->idp.ipaidpScope) {
|
||||
args[args_idx++] = "--scope";
|
||||
args[args_idx++] = (*item)->idp.ipaidpScope;
|
||||
}
|
||||
|
||||
if ((*item)->idp.ipaidpSub) {
|
||||
args[args_idx++] = "--user-identifier-attribute";
|
||||
args[args_idx++] = (*item)->idp.ipaidpSub;
|
||||
}
|
||||
|
||||
if ((*item)->idp.ipaidpDebugLevelStr != NULL) {
|
||||
args[args_idx++] = "--debug-level";
|
||||
args[args_idx++] = (*item)->idp.ipaidpDebugLevelStr;
|
||||
}
|
||||
|
||||
if ((*item)->idp.ipaidpDebugCurl) {
|
||||
args[args_idx++] = "--libcurl-debug";
|
||||
}
|
||||
|
||||
#if 0
|
||||
for (int i; args[i]; i++) {
|
||||
otpd_log_req((*item)->req, "oidc_child exec: %s", args[i]);
|
||||
}
|
||||
#endif
|
||||
|
||||
ret = pipe(pipefd_from_child);
|
||||
if (ret == -1) {
|
||||
ret = errno;
|
||||
goto done;
|
||||
}
|
||||
ret = pipe(pipefd_to_child);
|
||||
if (ret == -1) {
|
||||
ret = errno;
|
||||
goto done;
|
||||
}
|
||||
|
||||
child_pid = fork();
|
||||
|
||||
if (child_pid == 0) { /* child */
|
||||
close(pipefd_to_child[1]);
|
||||
ret = dup2(pipefd_to_child[0], STDIN_FILENO);
|
||||
if (ret == -1) {
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
close(pipefd_from_child[0]);
|
||||
ret = dup2(pipefd_from_child[1], STDOUT_FILENO);
|
||||
if (ret == -1) {
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
execv(OIDC_CHILD_PATH, args);
|
||||
exit(EXIT_FAILURE);
|
||||
} else if (child_pid > 0) { /* parent */
|
||||
close(pipefd_to_child[0]);
|
||||
set_fd_nonblocking(pipefd_to_child[1]);
|
||||
child_ctx->write_to_child = pipefd_to_child[1];
|
||||
|
||||
close(pipefd_from_child[1]);
|
||||
set_fd_nonblocking(pipefd_from_child[0]);
|
||||
child_ctx->read_from_child = pipefd_from_child[0];
|
||||
|
||||
child_ctx->write_ev = verto_add_io(ctx.vctx, VERTO_EV_FLAG_PERSIST |
|
||||
VERTO_EV_FLAG_IO_CLOSE_FD |
|
||||
VERTO_EV_FLAG_IO_ERROR |
|
||||
VERTO_EV_FLAG_IO_WRITE,
|
||||
oauth2_on_child_writable,
|
||||
child_ctx->write_to_child);
|
||||
if (child_ctx->write_ev == NULL) {
|
||||
ret = ENOMEM;
|
||||
otpd_log_err(ret, "Unable to initialize oauth2 writer event");
|
||||
goto done;
|
||||
}
|
||||
verto_set_private(child_ctx->write_ev, child_ctx, NULL);
|
||||
|
||||
child_ctx->read_ev = verto_add_io(ctx.vctx, VERTO_EV_FLAG_PERSIST |
|
||||
VERTO_EV_FLAG_IO_CLOSE_FD |
|
||||
VERTO_EV_FLAG_IO_ERROR |
|
||||
VERTO_EV_FLAG_IO_READ,
|
||||
oauth2_on_child_readable,
|
||||
child_ctx->read_from_child);
|
||||
if (child_ctx->read_ev == NULL) {
|
||||
ret = ENOMEM;
|
||||
otpd_log_err(ret, "Unable to initialize oauth2 writer event");
|
||||
goto done;
|
||||
}
|
||||
verto_set_private(child_ctx->read_ev, child_ctx, NULL);
|
||||
|
||||
child_ctx->child_ev = verto_add_child(ctx.vctx, VERTO_EV_FLAG_NONE,
|
||||
oauth2_on_child_exit, child_pid);
|
||||
verto_set_private(child_ctx->child_ev, child_ctx, free_child_ctx);
|
||||
|
||||
} else { /* error */
|
||||
ret = errno;
|
||||
otpd_log_err(ret, "Failed to fork oidc_child");
|
||||
goto done;
|
||||
}
|
||||
|
||||
ret = 0;
|
||||
done:
|
||||
if (ret == 0) {
|
||||
*item = NULL;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
@ -24,8 +24,11 @@
|
||||
* This file parses the user's configuration received from LDAP (see query.c).
|
||||
*/
|
||||
|
||||
#define _GNU_SOURCE /* for asprintf() */
|
||||
#include "internal.h"
|
||||
#include <asm-generic/errno-base.h>
|
||||
#include <ctype.h>
|
||||
#include <krb5/krb5.h>
|
||||
|
||||
#define DEFAULT_TIMEOUT 15
|
||||
#define DEFAULT_RETRIES 3
|
||||
@ -65,6 +68,69 @@ static int get_string(LDAP *ldp, LDAPMessage *entry, const char *name,
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Convert an LDAP entry into an allocated string array. */
|
||||
static int get_string_array(LDAP *ldp, LDAPMessage *entry, const char *name,
|
||||
char ***out)
|
||||
{
|
||||
struct berval **vals;
|
||||
ber_len_t i;
|
||||
char **buf;
|
||||
int tmp;
|
||||
size_t count;
|
||||
size_t c;
|
||||
int ret;
|
||||
|
||||
vals = ldap_get_values_len(ldp, entry, name);
|
||||
if (vals == NULL)
|
||||
return ENOENT;
|
||||
|
||||
tmp = ldap_count_values_len(vals);
|
||||
if (tmp < 0) {
|
||||
ret = ENOENT;
|
||||
goto done;
|
||||
}
|
||||
count = (size_t) tmp;
|
||||
|
||||
buf = calloc(count + 1, sizeof(char *));
|
||||
if (buf == NULL) {
|
||||
ret = ENOMEM;
|
||||
goto done;
|
||||
}
|
||||
|
||||
for (c = 0; c < count; c++) {
|
||||
buf[c] = calloc(vals[c]->bv_len + 1, sizeof(char));
|
||||
if (buf[c] == NULL) {
|
||||
ret = ENOMEM;
|
||||
goto done;
|
||||
}
|
||||
|
||||
for (i = 0; i < vals[c]->bv_len; i++) {
|
||||
if (!isprint(vals[c]->bv_val[i])) {
|
||||
ret = EINVAL;
|
||||
goto done;
|
||||
}
|
||||
|
||||
buf[c][i] = vals[c]->bv_val[i];
|
||||
}
|
||||
}
|
||||
|
||||
if (*out != NULL)
|
||||
free(*out);
|
||||
*out = buf;
|
||||
|
||||
ret = 0;
|
||||
|
||||
done:
|
||||
if (ret != 0 && buf != NULL) {
|
||||
for (c = 0; buf[c] != NULL; c++) {
|
||||
free(buf[c]);
|
||||
}
|
||||
free(buf);
|
||||
}
|
||||
ldap_value_free_len(vals);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* Convert an LDAP entry into an unsigned long. */
|
||||
static int get_ulong(LDAP *ldp, LDAPMessage *entry, const char *name,
|
||||
unsigned long *out)
|
||||
@ -112,6 +178,21 @@ const char *otpd_parse_user(LDAP *ldp, LDAPMessage *entry,
|
||||
if (i != 0 && i != ENOENT)
|
||||
return strerror(i);
|
||||
|
||||
i = get_string(ldp, entry, "ipaidpSub",
|
||||
&item->user.ipaidpSub);
|
||||
if (i != 0 && i != ENOENT)
|
||||
return strerror(i);
|
||||
|
||||
i = get_string(ldp, entry, "ipaidpConfigLink",
|
||||
&item->user.ipaidpConfigLink);
|
||||
if (i != 0 && i != ENOENT)
|
||||
return strerror(i);
|
||||
|
||||
i = get_string_array(ldp, entry, "ipauserauthtype",
|
||||
&item->user.ipauserauthtypes);
|
||||
if (i != 0 && i != ENOENT)
|
||||
return strerror(i);
|
||||
|
||||
/* Get the DN. */
|
||||
item->user.dn = ldap_get_dn(ldp, entry);
|
||||
if (item->user.dn == NULL) {
|
||||
@ -122,6 +203,101 @@ const char *otpd_parse_user(LDAP *ldp, LDAPMessage *entry,
|
||||
return NULL;
|
||||
}
|
||||
|
||||
#define ENV_OIDC_CHILD_DEBUG_LEVEL "oidc_child_debug_level"
|
||||
/* Parse the IdP configuration */
|
||||
const char *otpd_parse_idp(LDAP *ldp, LDAPMessage *entry,
|
||||
struct otpd_queue_item *item)
|
||||
{
|
||||
int i;
|
||||
long dbg_lvl = 0;
|
||||
const char *dbg_env = NULL;
|
||||
char *endptr = NULL;
|
||||
|
||||
item->idp.valid = FALSE;
|
||||
i = get_string(ldp, entry, "cn", &item->idp.name);
|
||||
if (i != 0) {
|
||||
return strerror(i);
|
||||
}
|
||||
|
||||
i = get_string(ldp, entry, "ipaidpIssuerURL", &item->idp.ipaidpIssuerURL);
|
||||
if ((i != 0) && (i != ENOENT)) {
|
||||
return strerror(i);
|
||||
}
|
||||
|
||||
/* We support either passing issuer URL or individual end-points */
|
||||
if (i == ENOENT) {
|
||||
i = get_string(ldp, entry, "ipaidpDevAuthEndpoint", &item->idp.ipaidpDevAuthEndpoint);
|
||||
if (i != 0) {
|
||||
return strerror(i);
|
||||
}
|
||||
|
||||
i = get_string(ldp, entry, "ipaidpTokenEndpoint", &item->idp.ipaidpTokenEndpoint);
|
||||
if (i != 0) {
|
||||
return strerror(i);
|
||||
}
|
||||
|
||||
i = get_string(ldp, entry, "ipaidpUserInfoEndpoint", &item->idp.ipaidpUserInfoEndpoint);
|
||||
if (i != 0) {
|
||||
return strerror(i);
|
||||
}
|
||||
|
||||
/* JWKS end-point may be optional */
|
||||
i = get_string(ldp, entry, "ipaidpKeysEndpoint", &item->idp.ipaidpKeysEndpoint);
|
||||
if ((i != 0) && (i != ENOENT)) {
|
||||
return strerror(i);
|
||||
}
|
||||
}
|
||||
|
||||
i = get_string(ldp, entry, "ipaidpClientID", &item->idp.ipaidpClientID);
|
||||
if (i != 0) {
|
||||
return strerror(i);
|
||||
}
|
||||
|
||||
i = get_string(ldp, entry, "ipaidpClientSecret", &item->idp.ipaidpClientSecret);
|
||||
if ((i != 0) && (i != ENOENT)) {
|
||||
return strerror(i);
|
||||
}
|
||||
|
||||
i = get_string(ldp, entry, "ipaidpScope", &item->idp.ipaidpScope);
|
||||
if ((i != 0) && (i != ENOENT)) {
|
||||
return strerror(i);
|
||||
}
|
||||
|
||||
i = get_string(ldp, entry, "ipaidpSub", &item->idp.ipaidpSub);
|
||||
if ((i != 0) && (i != ENOENT)) {
|
||||
return strerror(i);
|
||||
}
|
||||
|
||||
item->idp.ipaidpDebugLevelStr = NULL;
|
||||
item->idp.ipaidpDebugCurl = FALSE;
|
||||
dbg_env = getenv(ENV_OIDC_CHILD_DEBUG_LEVEL);
|
||||
if (dbg_env != NULL && *dbg_env != '\0') {
|
||||
errno = 0;
|
||||
dbg_lvl = strtoul(dbg_env, &endptr, 10);
|
||||
if (errno == 0 && *endptr == '\0') {
|
||||
if (dbg_lvl < 0) {
|
||||
dbg_lvl = 0;
|
||||
} else if (dbg_lvl > 10) {
|
||||
dbg_lvl = 10;
|
||||
}
|
||||
if (asprintf(&item->idp.ipaidpDebugLevelStr, "%ld", dbg_lvl) != -1) {
|
||||
if (dbg_lvl > 5) {
|
||||
item->idp.ipaidpDebugCurl = TRUE;
|
||||
}
|
||||
} else {
|
||||
otpd_log_req(item->req, "Failed to copy debug level");
|
||||
}
|
||||
} else {
|
||||
otpd_log_req(item->req,
|
||||
"Cannot parse value [%s] from environment variable [%s]",
|
||||
dbg_env, ENV_OIDC_CHILD_DEBUG_LEVEL);
|
||||
}
|
||||
}
|
||||
|
||||
item->idp.valid = TRUE;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Parse the user's RADIUS configuration. */
|
||||
const char *otpd_parse_radius(LDAP *ldp, LDAPMessage *entry,
|
||||
struct otpd_queue_item *item)
|
||||
|
@ -31,6 +31,7 @@
|
||||
#define _GNU_SOURCE 1 /* for asprintf() */
|
||||
#include "internal.h"
|
||||
#include <ctype.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
#define DEFAULT_TIMEOUT 15
|
||||
#define DEFAULT_RETRIES 3
|
||||
@ -39,6 +40,9 @@ static char *user[] = {
|
||||
"uid",
|
||||
"ipatokenRadiusUserName",
|
||||
"ipatokenRadiusConfigLink",
|
||||
"ipaidpSub",
|
||||
"ipaidpConfigLink",
|
||||
"ipauserauthtype",
|
||||
NULL
|
||||
};
|
||||
|
||||
@ -51,6 +55,37 @@ static char *radius[] = {
|
||||
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)
|
||||
{
|
||||
@ -76,6 +111,7 @@ static void on_query_writable(verto_ctx *vctx, verto_ev *ev)
|
||||
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)
|
||||
@ -86,9 +122,20 @@ static void on_query_writable(verto_ctx *vctx, verto_ev *ev)
|
||||
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,
|
||||
@ -98,6 +145,7 @@ static void on_query_writable(verto_ctx *vctx, verto_ev *ev)
|
||||
} 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;
|
||||
@ -107,7 +155,6 @@ static void on_query_writable(verto_ctx *vctx, verto_ev *ev)
|
||||
}
|
||||
|
||||
if (i == LDAP_SUCCESS) {
|
||||
item->sent++;
|
||||
push = &ctx.query.responses;
|
||||
}
|
||||
|
||||
@ -115,6 +162,78 @@ 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)
|
||||
{
|
||||
@ -126,6 +245,7 @@ static void on_query_readable(verto_ctx *vctx, verto_ev *ev)
|
||||
LDAP *ldp;
|
||||
int i;
|
||||
(void)vctx;
|
||||
enum oauth2_state oauth2_state;
|
||||
|
||||
ldp = verto_get_private(ev);
|
||||
|
||||
@ -150,16 +270,19 @@ static void on_query_readable(verto_ctx *vctx, verto_ev *ev)
|
||||
goto egress;
|
||||
|
||||
err = NULL;
|
||||
switch (item->sent) {
|
||||
case 1:
|
||||
switch (item->ldap_query) {
|
||||
case LDAP_QUERY_USER:
|
||||
err = otpd_parse_user(ldp, entry, item);
|
||||
break;
|
||||
case 2:
|
||||
case LDAP_QUERY_RADIUS:
|
||||
err = otpd_parse_radius(ldp, entry, item);
|
||||
break;
|
||||
case 3:
|
||||
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;
|
||||
@ -181,14 +304,14 @@ static void on_query_readable(verto_ctx *vctx, verto_ev *ev)
|
||||
|
||||
item->msgid = -1;
|
||||
|
||||
switch (item->sent) {
|
||||
case 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 2:
|
||||
case LDAP_QUERY_RADIUS:
|
||||
otpd_log_req(item->req, "radius query end: %s",
|
||||
item->error == NULL
|
||||
? item->radius.ipatokenRadiusServer
|
||||
@ -197,22 +320,47 @@ static void on_query_readable(verto_ctx *vctx, verto_ev *ev)
|
||||
item->radius.ipatokenRadiusSecret == NULL)
|
||||
goto egress;
|
||||
break;
|
||||
case 3:
|
||||
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;
|
||||
}
|
||||
|
||||
if (item->error != NULL)
|
||||
goto egress;
|
||||
|
||||
if (item->sent == 1 && item->user.ipatokenRadiusConfigLink != NULL) {
|
||||
/* 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 (item->sent == 2 &&
|
||||
} 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;
|
||||
|
@ -46,6 +46,8 @@ krb5_error_code otpd_queue_item_new(krad_packet *req,
|
||||
|
||||
void otpd_queue_item_free(struct otpd_queue_item *item)
|
||||
{
|
||||
size_t c;
|
||||
|
||||
if (item == NULL)
|
||||
return;
|
||||
|
||||
@ -54,9 +56,34 @@ void otpd_queue_item_free(struct otpd_queue_item *item)
|
||||
free(item->user.ipatokenRadiusUserName);
|
||||
free(item->user.ipatokenRadiusConfigLink);
|
||||
free(item->user.other);
|
||||
free(item->user.ipaidpSub);
|
||||
free(item->user.ipaidpConfigLink);
|
||||
if (item->user.ipauserauthtypes != NULL) {
|
||||
for (c = 0; item->user.ipauserauthtypes[c] != NULL; c++) {
|
||||
free(item->user.ipauserauthtypes[c]);
|
||||
}
|
||||
free(item->user.ipauserauthtypes);
|
||||
}
|
||||
free(item->radius.ipatokenRadiusServer);
|
||||
free(item->radius.ipatokenRadiusSecret);
|
||||
free(item->radius.ipatokenUserMapAttribute);
|
||||
free(item->idp.ipaidpIssuerURL);
|
||||
free(item->idp.ipaidpDevAuthEndpoint);
|
||||
free(item->idp.ipaidpTokenEndpoint);
|
||||
free(item->idp.ipaidpUserInfoEndpoint);
|
||||
free(item->idp.ipaidpKeysEndpoint);
|
||||
free(item->idp.name);
|
||||
free(item->idp.ipaidpClientID);
|
||||
if (item->idp.ipaidpClientSecret != NULL) {
|
||||
size_t len = strlen(item->idp.ipaidpClientSecret);
|
||||
(void*) memset(item->idp.ipaidpClientSecret, 0, len);
|
||||
free(item->idp.ipaidpClientSecret);
|
||||
}
|
||||
free(item->idp.ipaidpScope);
|
||||
free(item->idp.ipaidpSub);
|
||||
free(item->idp.ipaidpDebugLevelStr);
|
||||
free(item->oauth2.device_code_reply);
|
||||
free(item->oauth2.state.data);
|
||||
free(item->error);
|
||||
krad_packet_free(item->req);
|
||||
krad_packet_free(item->rsp);
|
||||
|
@ -166,6 +166,7 @@ void otpd_on_stdout_writable(verto_ctx *vctx, verto_ev *ev)
|
||||
|
||||
/* Send the packet. */
|
||||
data = krad_packet_encode(item->rsp);
|
||||
otpd_log_req(item->req, "sent: %d data: %d", item->sent, data->length);
|
||||
i = write(verto_get_fd(ev), data->data + item->sent,
|
||||
data->length - item->sent);
|
||||
if (i < 0) {
|
||||
@ -191,6 +192,7 @@ void otpd_on_stdout_writable(verto_ctx *vctx, verto_ev *ev)
|
||||
|
||||
/* If the packet was completely sent, free the response. */
|
||||
item->sent += i;
|
||||
otpd_log_req(item->req, "..sent: %d data: %d", item->sent, data->length);
|
||||
if (item->sent == data->length) {
|
||||
otpd_log_req(item->req, "response sent: %s",
|
||||
krad_code_num2name(krad_packet_get_code(item->rsp)));
|
||||
|
Loading…
Reference in New Issue
Block a user