Import Upstream version 4.12.4
This commit is contained in:
@@ -1,13 +1,16 @@
|
||||
AM_CPPFLAGS := -I$(top_srcdir)/util
|
||||
AM_CFLAGS := @LDAP_CFLAGS@ @LIBVERTO_CFLAGS@ @KRB5_CFLAGS@ @NSPR_CFLAGS@
|
||||
AM_LDFLAGS := @LDAP_LIBS@ @LIBVERTO_LIBS@ @KRAD_LIBS@ @KRB5_LIBS@
|
||||
AM_LDFLAGS := @LDAP_LIBS@ @LIBVERTO_LIBS@ @KRAD_LIBS@ @KRB5_LIBS@ @JANSSON_LIBS@
|
||||
|
||||
noinst_HEADERS = internal.h
|
||||
appdir = $(libexecdir)/ipa/
|
||||
app_PROGRAMS = ipa-otpd
|
||||
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 passkey.c
|
||||
|
||||
%.socket: %.socket.in
|
||||
@sed -e 's|@krb5rundir[@]|$(krb5rundir)|g' \
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# Makefile.in generated by automake 1.16.2 from Makefile.am.
|
||||
# Makefile.in generated by automake 1.17 from Makefile.am.
|
||||
# @configure_input@
|
||||
|
||||
# Copyright (C) 1994-2020 Free Software Foundation, Inc.
|
||||
# Copyright (C) 1994-2024 Free Software Foundation, Inc.
|
||||
|
||||
# This Makefile.in is free software; the Free Software Foundation
|
||||
# gives unlimited permission to copy and/or distribute it,
|
||||
@@ -72,6 +72,8 @@ am__make_running_with_option = \
|
||||
test $$has_opt = yes
|
||||
am__make_dryrun = (target_option=n; $(am__make_running_with_option))
|
||||
am__make_keepgoing = (target_option=k; $(am__make_running_with_option))
|
||||
am__rm_f = rm -f $(am__rm_f_notfound)
|
||||
am__rm_rf = rm -rf $(am__rm_f_notfound)
|
||||
pkgdatadir = $(datadir)/@PACKAGE@
|
||||
pkgincludedir = $(includedir)/@PACKAGE@
|
||||
pkglibdir = $(libdir)/@PACKAGE@
|
||||
@@ -120,9 +122,9 @@ am__installdirs = "$(DESTDIR)$(appdir)" \
|
||||
PROGRAMS = $(app_PROGRAMS)
|
||||
am_ipa_otpd_OBJECTS = bind.$(OBJEXT) forward.$(OBJEXT) main.$(OBJEXT) \
|
||||
parse.$(OBJEXT) query.$(OBJEXT) queue.$(OBJEXT) \
|
||||
stdio.$(OBJEXT)
|
||||
stdio.$(OBJEXT) oauth2.$(OBJEXT) passkey.$(OBJEXT)
|
||||
ipa_otpd_OBJECTS = $(am_ipa_otpd_OBJECTS)
|
||||
ipa_otpd_LDADD = $(LDADD)
|
||||
ipa_otpd_DEPENDENCIES = $(top_builddir)/util/libutil.la
|
||||
AM_V_lt = $(am__v_lt_@AM_V@)
|
||||
am__v_lt_ = $(am__v_lt_@AM_DEFAULT_V@)
|
||||
am__v_lt_0 = --silent
|
||||
@@ -152,7 +154,8 @@ DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir)
|
||||
depcomp = $(SHELL) $(top_srcdir)/depcomp
|
||||
am__maybe_remake_depfiles = depfiles
|
||||
am__depfiles_remade = ./$(DEPDIR)/bind.Po ./$(DEPDIR)/forward.Po \
|
||||
./$(DEPDIR)/main.Po ./$(DEPDIR)/parse.Po ./$(DEPDIR)/query.Po \
|
||||
./$(DEPDIR)/main.Po ./$(DEPDIR)/oauth2.Po ./$(DEPDIR)/parse.Po \
|
||||
./$(DEPDIR)/passkey.Po ./$(DEPDIR)/query.Po \
|
||||
./$(DEPDIR)/queue.Po \
|
||||
./$(DEPDIR)/queue_tests-ipa_otpd_queue_cmocka_tests.Po \
|
||||
./$(DEPDIR)/queue_tests-queue.Po ./$(DEPDIR)/stdio.Po
|
||||
@@ -204,10 +207,9 @@ am__base_list = \
|
||||
sed '$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;s/\n/ /g' | \
|
||||
sed '$$!N;$$!N;$$!N;$$!N;s/\n/ /g'
|
||||
am__uninstall_files_from_dir = { \
|
||||
test -z "$$files" \
|
||||
|| { test ! -d "$$dir" && test ! -f "$$dir" && test ! -r "$$dir"; } \
|
||||
|| { echo " ( cd '$$dir' && rm -f" $$files ")"; \
|
||||
$(am__cd) "$$dir" && rm -f $$files; }; \
|
||||
{ test ! -d "$$dir" && test ! -f "$$dir" && test ! -r "$$dir"; } \
|
||||
|| { echo " ( cd '$$dir' && rm -f" $$files ")"; \
|
||||
$(am__cd) "$$dir" && echo $$files | $(am__xargs_n) 40 $(am__rm_f); }; \
|
||||
}
|
||||
DATA = $(dist_noinst_DATA) $(systemdsystemunit_DATA)
|
||||
HEADERS = $(noinst_HEADERS)
|
||||
@@ -228,8 +230,6 @@ am__define_uniq_tagged_files = \
|
||||
unique=`for i in $$list; do \
|
||||
if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
|
||||
done | $(am__uniquify_input)`
|
||||
ETAGS = etags
|
||||
CTAGS = ctags
|
||||
am__tty_colors_dummy = \
|
||||
mgn= red= grn= lgn= blu= brg= std=; \
|
||||
am__color_tests=no
|
||||
@@ -338,6 +338,7 @@ am__sh_e_setup = case $$- in *e*) set +e;; esac
|
||||
# Default flags passed to test drivers.
|
||||
am__common_driver_flags = \
|
||||
--color-tests "$$am__color_tests" \
|
||||
$$am__collect_skipped_logs \
|
||||
--enable-hard-errors "$$am__enable_hard_errors" \
|
||||
--expect-failure "$$am__expect_failure"
|
||||
# To be inserted before the command running the test. Creates the
|
||||
@@ -362,6 +363,11 @@ if test -f "./$$f"; then dir=./; \
|
||||
elif test -f "$$f"; then dir=; \
|
||||
else dir="$(srcdir)/"; fi; \
|
||||
tst=$$dir$$f; log='$@'; \
|
||||
if test -n '$(IGNORE_SKIPPED_LOGS)'; then \
|
||||
am__collect_skipped_logs='--collect-skipped-logs no'; \
|
||||
else \
|
||||
am__collect_skipped_logs=''; \
|
||||
fi; \
|
||||
if test -n '$(DISABLE_HARD_ERRORS)'; then \
|
||||
am__enable_hard_errors=no; \
|
||||
else \
|
||||
@@ -385,6 +391,7 @@ am__set_TESTS_bases = \
|
||||
bases='$(TEST_LOGS)'; \
|
||||
bases=`for i in $$bases; do echo $$i; done | sed 's/\.log$$//'`; \
|
||||
bases=`echo $$bases`
|
||||
AM_TESTSUITE_SUMMARY_HEADER = ' for $(PACKAGE_STRING)'
|
||||
RECHECK_LOGS = $(TEST_LOGS)
|
||||
AM_RECURSIVE_TARGETS = check recheck
|
||||
TEST_SUITE_LOG = test-suite.log
|
||||
@@ -429,6 +436,8 @@ CPP = @CPP@
|
||||
CPPFLAGS = @CPPFLAGS@
|
||||
CRYPTO_CFLAGS = @CRYPTO_CFLAGS@
|
||||
CRYPTO_LIBS = @CRYPTO_LIBS@
|
||||
CSCOPE = @CSCOPE@
|
||||
CTAGS = @CTAGS@
|
||||
CYGPATH_W = @CYGPATH_W@
|
||||
DATA_VERSION = @DATA_VERSION@
|
||||
DEFS = @DEFS@
|
||||
@@ -442,8 +451,10 @@ ECHO_C = @ECHO_C@
|
||||
ECHO_N = @ECHO_N@
|
||||
ECHO_T = @ECHO_T@
|
||||
EGREP = @EGREP@
|
||||
ETAGS = @ETAGS@
|
||||
EXEEXT = @EXEEXT@
|
||||
FGREP = @FGREP@
|
||||
FILECMD = @FILECMD@
|
||||
GETTEXT_DOMAIN = @GETTEXT_DOMAIN@
|
||||
GETTEXT_MACRO_VERSION = @GETTEXT_MACRO_VERSION@
|
||||
GIT_BRANCH = @GIT_BRANCH@
|
||||
@@ -451,6 +462,7 @@ GIT_VERSION = @GIT_VERSION@
|
||||
GMSGFMT = @GMSGFMT@
|
||||
GMSGFMT_015 = @GMSGFMT_015@
|
||||
GREP = @GREP@
|
||||
HTTPD_GROUP = @HTTPD_GROUP@
|
||||
INI_CFLAGS = @INI_CFLAGS@
|
||||
INI_LIBS = @INI_LIBS@
|
||||
INSTALL = @INSTALL@
|
||||
@@ -463,9 +475,12 @@ INTL_MACOSX_LIBS = @INTL_MACOSX_LIBS@
|
||||
IPAPLATFORM = @IPAPLATFORM@
|
||||
IPA_DATA_DIR = @IPA_DATA_DIR@
|
||||
IPA_SYSCONF_DIR = @IPA_SYSCONF_DIR@
|
||||
JANSSON_CFLAGS = @JANSSON_CFLAGS@
|
||||
JANSSON_LIBS = @JANSSON_LIBS@
|
||||
JSLINT = @JSLINT@
|
||||
KRAD_LIBS = @KRAD_LIBS@
|
||||
KRB5KDC_SERVICE = @KRB5KDC_SERVICE@
|
||||
KRB5_BUILD_VERSION = @KRB5_BUILD_VERSION@
|
||||
KRB5_CFLAGS = @KRB5_CFLAGS@
|
||||
KRB5_GSSAPI_CFLAGS = @KRB5_GSSAPI_CFLAGS@
|
||||
KRB5_GSSAPI_LIBS = @KRB5_GSSAPI_LIBS@
|
||||
@@ -474,6 +489,8 @@ LD = @LD@
|
||||
LDAP_CFLAGS = @LDAP_CFLAGS@
|
||||
LDAP_LIBS = @LDAP_LIBS@
|
||||
LDFLAGS = @LDFLAGS@
|
||||
LIBCURL_CFLAGS = @LIBCURL_CFLAGS@
|
||||
LIBCURL_LIBS = @LIBCURL_LIBS@
|
||||
LIBICONV = @LIBICONV@
|
||||
LIBINTL = @LIBINTL@
|
||||
LIBINTL_LIBS = @LIBINTL_LIBS@
|
||||
@@ -533,6 +550,8 @@ PLATFORM_PYTHON = @PLATFORM_PYTHON@
|
||||
POPT_CFLAGS = @POPT_CFLAGS@
|
||||
POPT_LIBS = @POPT_LIBS@
|
||||
POSUB = @POSUB@
|
||||
PWQUALITY_CFLAGS = @PWQUALITY_CFLAGS@
|
||||
PWQUALITY_LIBS = @PWQUALITY_LIBS@
|
||||
PYLINT = @PYLINT@
|
||||
PYTHON = @PYTHON@
|
||||
PYTHON_EXEC_PREFIX = @PYTHON_EXEC_PREFIX@
|
||||
@@ -541,9 +560,12 @@ PYTHON_PLATFORM = @PYTHON_PLATFORM@
|
||||
PYTHON_PREFIX = @PYTHON_PREFIX@
|
||||
PYTHON_VERSION = @PYTHON_VERSION@
|
||||
RANLIB = @RANLIB@
|
||||
RESOLV_LIBS = @RESOLV_LIBS@
|
||||
RPMLINT = @RPMLINT@
|
||||
SAMBA40EXTRA_LIBPATH = @SAMBA40EXTRA_LIBPATH@
|
||||
SAMBAUTIL_CFLAGS = @SAMBAUTIL_CFLAGS@
|
||||
SAMBAUTIL_LIBS = @SAMBAUTIL_LIBS@
|
||||
SAMBA_SECURITY_LIBS = @SAMBA_SECURITY_LIBS@
|
||||
SASL_CFLAGS = @SASL_CFLAGS@
|
||||
SASL_LIBS = @SASL_LIBS@
|
||||
SED = @SED@
|
||||
@@ -582,8 +604,10 @@ ac_ct_DUMPBIN = @ac_ct_DUMPBIN@
|
||||
am__include = @am__include@
|
||||
am__leading_dot = @am__leading_dot@
|
||||
am__quote = @am__quote@
|
||||
am__rm_f_notfound = @am__rm_f_notfound@
|
||||
am__tar = @am__tar@
|
||||
am__untar = @am__untar@
|
||||
am__xargs_n = @am__xargs_n@
|
||||
bindir = @bindir@
|
||||
build = @build@
|
||||
build_alias = @build_alias@
|
||||
@@ -629,19 +653,24 @@ sharedstatedir = @sharedstatedir@
|
||||
srcdir = @srcdir@
|
||||
sysconfdir = @sysconfdir@
|
||||
sysconfenvdir = @sysconfenvdir@
|
||||
systemdcatalogdir = @systemdcatalogdir@
|
||||
systemdsystemunitdir = @systemdsystemunitdir@
|
||||
systemdtmpfilesdir = @systemdtmpfilesdir@
|
||||
target_alias = @target_alias@
|
||||
top_build_prefix = @top_build_prefix@
|
||||
top_builddir = @top_builddir@
|
||||
top_srcdir = @top_srcdir@
|
||||
AM_CPPFLAGS := -I$(top_srcdir)/util
|
||||
AM_CFLAGS := @LDAP_CFLAGS@ @LIBVERTO_CFLAGS@ @KRB5_CFLAGS@ @NSPR_CFLAGS@
|
||||
AM_LDFLAGS := @LDAP_LIBS@ @LIBVERTO_LIBS@ @KRAD_LIBS@ @KRB5_LIBS@
|
||||
AM_LDFLAGS := @LDAP_LIBS@ @LIBVERTO_LIBS@ @KRAD_LIBS@ @KRB5_LIBS@ @JANSSON_LIBS@
|
||||
noinst_HEADERS = internal.h
|
||||
appdir = $(libexecdir)/ipa/
|
||||
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 passkey.c
|
||||
|
||||
CLEANFILES = $(systemdsystemunit_DATA)
|
||||
queue_tests_SOURCES = ipa_otpd_queue_cmocka_tests.c queue.c
|
||||
queue_tests_CFLAGS = $(CMOCKA_CFLAGS)
|
||||
@@ -718,25 +747,15 @@ uninstall-appPROGRAMS:
|
||||
`; \
|
||||
test -n "$$list" || exit 0; \
|
||||
echo " ( cd '$(DESTDIR)$(appdir)' && rm -f" $$files ")"; \
|
||||
cd "$(DESTDIR)$(appdir)" && rm -f $$files
|
||||
cd "$(DESTDIR)$(appdir)" && $(am__rm_f) $$files
|
||||
|
||||
clean-appPROGRAMS:
|
||||
@list='$(app_PROGRAMS)'; test -n "$$list" || exit 0; \
|
||||
echo " rm -f" $$list; \
|
||||
rm -f $$list || exit $$?; \
|
||||
test -n "$(EXEEXT)" || exit 0; \
|
||||
list=`for p in $$list; do echo "$$p"; done | sed 's/$(EXEEXT)$$//'`; \
|
||||
echo " rm -f" $$list; \
|
||||
rm -f $$list
|
||||
$(am__rm_f) $(app_PROGRAMS)
|
||||
test -z "$(EXEEXT)" || $(am__rm_f) $(app_PROGRAMS:$(EXEEXT)=)
|
||||
|
||||
clean-checkPROGRAMS:
|
||||
@list='$(check_PROGRAMS)'; test -n "$$list" || exit 0; \
|
||||
echo " rm -f" $$list; \
|
||||
rm -f $$list || exit $$?; \
|
||||
test -n "$(EXEEXT)" || exit 0; \
|
||||
list=`for p in $$list; do echo "$$p"; done | sed 's/$(EXEEXT)$$//'`; \
|
||||
echo " rm -f" $$list; \
|
||||
rm -f $$list
|
||||
$(am__rm_f) $(check_PROGRAMS)
|
||||
test -z "$(EXEEXT)" || $(am__rm_f) $(check_PROGRAMS:$(EXEEXT)=)
|
||||
|
||||
ipa-otpd$(EXEEXT): $(ipa_otpd_OBJECTS) $(ipa_otpd_DEPENDENCIES) $(EXTRA_ipa_otpd_DEPENDENCIES)
|
||||
@rm -f ipa-otpd$(EXEEXT)
|
||||
@@ -755,7 +774,9 @@ distclean-compile:
|
||||
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/bind.Po@am__quote@ # am--include-marker
|
||||
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/forward.Po@am__quote@ # am--include-marker
|
||||
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/main.Po@am__quote@ # am--include-marker
|
||||
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/oauth2.Po@am__quote@ # am--include-marker
|
||||
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/parse.Po@am__quote@ # am--include-marker
|
||||
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/passkey.Po@am__quote@ # am--include-marker
|
||||
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/query.Po@am__quote@ # am--include-marker
|
||||
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/queue.Po@am__quote@ # am--include-marker
|
||||
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/queue_tests-ipa_otpd_queue_cmocka_tests.Po@am__quote@ # am--include-marker
|
||||
@@ -764,7 +785,7 @@ distclean-compile:
|
||||
|
||||
$(am__depfiles_remade):
|
||||
@$(MKDIR_P) $(@D)
|
||||
@echo '# dummy' >$@-t && $(am__mv) $@-t $@
|
||||
@: >>$@
|
||||
|
||||
am--depfiles: $(am__depfiles_remade)
|
||||
|
||||
@@ -909,7 +930,6 @@ distclean-tags:
|
||||
am--fnord $(TEST_LOGS) $(TEST_LOGS:.log=.trs): $(am__force_recheck)
|
||||
am--force-recheck:
|
||||
@:
|
||||
|
||||
$(TEST_SUITE_LOG): $(TEST_LOGS)
|
||||
@$(am__set_TESTS_bases); \
|
||||
am__f_ok () { test -f "$$1" && test -r "$$1"; }; \
|
||||
@@ -985,10 +1005,37 @@ $(TEST_SUITE_LOG): $(TEST_LOGS)
|
||||
result_count $$1 "XPASS:" $$xpass "$$red"; \
|
||||
result_count $$1 "ERROR:" $$error "$$mgn"; \
|
||||
}; \
|
||||
output_system_information () \
|
||||
{ \
|
||||
echo; \
|
||||
{ uname -a | $(AWK) '{ \
|
||||
printf "System information (uname -a):"; \
|
||||
for (i = 1; i < NF; ++i) \
|
||||
{ \
|
||||
if (i != 2) \
|
||||
printf " %s", $$i; \
|
||||
} \
|
||||
printf "\n"; \
|
||||
}'; } 2>&1; \
|
||||
if test -r /etc/os-release; then \
|
||||
echo "Distribution information (/etc/os-release):"; \
|
||||
sed 8q /etc/os-release; \
|
||||
elif test -r /etc/issue; then \
|
||||
echo "Distribution information (/etc/issue):"; \
|
||||
cat /etc/issue; \
|
||||
fi; \
|
||||
}; \
|
||||
please_report () \
|
||||
{ \
|
||||
echo "Some test(s) failed. Please report this to $(PACKAGE_BUGREPORT),"; \
|
||||
echo "together with the test-suite.log file (gzipped) and your system"; \
|
||||
echo "information. Thanks."; \
|
||||
}; \
|
||||
{ \
|
||||
echo "$(PACKAGE_STRING): $(subdir)/$(TEST_SUITE_LOG)" | \
|
||||
$(am__rst_title); \
|
||||
create_testsuite_report --no-color; \
|
||||
output_system_information; \
|
||||
echo; \
|
||||
echo ".. contents:: :depth: 2"; \
|
||||
echo; \
|
||||
@@ -1003,31 +1050,30 @@ $(TEST_SUITE_LOG): $(TEST_LOGS)
|
||||
test x"$$VERBOSE" = x || cat $(TEST_SUITE_LOG); \
|
||||
fi; \
|
||||
echo "$${col}$$br$${std}"; \
|
||||
echo "$${col}Testsuite summary for $(PACKAGE_STRING)$${std}"; \
|
||||
echo "$${col}Testsuite summary"$(AM_TESTSUITE_SUMMARY_HEADER)"$${std}"; \
|
||||
echo "$${col}$$br$${std}"; \
|
||||
create_testsuite_report --maybe-color; \
|
||||
echo "$$col$$br$$std"; \
|
||||
if $$success; then :; else \
|
||||
echo "$${col}See $(subdir)/$(TEST_SUITE_LOG)$${std}"; \
|
||||
echo "$${col}See $(subdir)/$(TEST_SUITE_LOG) for debugging.$${std}";\
|
||||
if test -n "$(PACKAGE_BUGREPORT)"; then \
|
||||
echo "$${col}Please report to $(PACKAGE_BUGREPORT)$${std}"; \
|
||||
please_report | sed -e "s/^/$${col}/" -e s/'$$'/"$${std}"/; \
|
||||
fi; \
|
||||
echo "$$col$$br$$std"; \
|
||||
fi; \
|
||||
$$success || exit 1
|
||||
|
||||
check-TESTS: $(check_PROGRAMS)
|
||||
@list='$(RECHECK_LOGS)'; test -z "$$list" || rm -f $$list
|
||||
@list='$(RECHECK_LOGS:.log=.trs)'; test -z "$$list" || rm -f $$list
|
||||
@test -z "$(TEST_SUITE_LOG)" || rm -f $(TEST_SUITE_LOG)
|
||||
@$(am__rm_f) $(RECHECK_LOGS)
|
||||
@$(am__rm_f) $(RECHECK_LOGS:.log=.trs)
|
||||
@$(am__rm_f) $(TEST_SUITE_LOG)
|
||||
@set +e; $(am__set_TESTS_bases); \
|
||||
log_list=`for i in $$bases; do echo $$i.log; done`; \
|
||||
trs_list=`for i in $$bases; do echo $$i.trs; done`; \
|
||||
log_list=`echo $$log_list`; trs_list=`echo $$trs_list`; \
|
||||
log_list=`echo $$log_list`; \
|
||||
$(MAKE) $(AM_MAKEFLAGS) $(TEST_SUITE_LOG) TEST_LOGS="$$log_list"; \
|
||||
exit $$?;
|
||||
recheck: all $(check_PROGRAMS)
|
||||
@test -z "$(TEST_SUITE_LOG)" || rm -f $(TEST_SUITE_LOG)
|
||||
@$(am__rm_f) $(TEST_SUITE_LOG)
|
||||
@set +e; $(am__set_TESTS_bases); \
|
||||
bases=`for i in $$bases; do echo $$i; done \
|
||||
| $(am__list_recheck_tests)` || exit 1; \
|
||||
@@ -1058,7 +1104,6 @@ queue_tests.log: queue_tests$(EXEEXT)
|
||||
@am__EXEEXT_TRUE@ --log-file $$b.log --trs-file $$b.trs \
|
||||
@am__EXEEXT_TRUE@ $(am__common_driver_flags) $(AM_TEST_LOG_DRIVER_FLAGS) $(TEST_LOG_DRIVER_FLAGS) -- $(TEST_LOG_COMPILE) \
|
||||
@am__EXEEXT_TRUE@ "$$tst" $(AM_TESTS_FD_REDIRECT)
|
||||
|
||||
distdir: $(BUILT_SOURCES)
|
||||
$(MAKE) $(AM_MAKEFLAGS) distdir-am
|
||||
|
||||
@@ -1121,16 +1166,16 @@ install-strip:
|
||||
"INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \
|
||||
fi
|
||||
mostlyclean-generic:
|
||||
-test -z "$(TEST_LOGS)" || rm -f $(TEST_LOGS)
|
||||
-test -z "$(TEST_LOGS:.log=.trs)" || rm -f $(TEST_LOGS:.log=.trs)
|
||||
-test -z "$(TEST_SUITE_LOG)" || rm -f $(TEST_SUITE_LOG)
|
||||
-$(am__rm_f) $(TEST_LOGS)
|
||||
-$(am__rm_f) $(TEST_LOGS:.log=.trs)
|
||||
-$(am__rm_f) $(TEST_SUITE_LOG)
|
||||
|
||||
clean-generic:
|
||||
-test -z "$(CLEANFILES)" || rm -f $(CLEANFILES)
|
||||
-$(am__rm_f) $(CLEANFILES)
|
||||
|
||||
distclean-generic:
|
||||
-test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES)
|
||||
-test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES)
|
||||
-$(am__rm_f) $(CONFIG_CLEAN_FILES)
|
||||
-test . = "$(srcdir)" || $(am__rm_f) $(CONFIG_CLEAN_VPATH_FILES)
|
||||
|
||||
maintainer-clean-generic:
|
||||
@echo "This command is intended for maintainers to use"
|
||||
@@ -1141,10 +1186,12 @@ clean-am: clean-appPROGRAMS clean-checkPROGRAMS clean-generic \
|
||||
clean-libtool mostlyclean-am
|
||||
|
||||
distclean: distclean-am
|
||||
-rm -f ./$(DEPDIR)/bind.Po
|
||||
-rm -f ./$(DEPDIR)/bind.Po
|
||||
-rm -f ./$(DEPDIR)/forward.Po
|
||||
-rm -f ./$(DEPDIR)/main.Po
|
||||
-rm -f ./$(DEPDIR)/oauth2.Po
|
||||
-rm -f ./$(DEPDIR)/parse.Po
|
||||
-rm -f ./$(DEPDIR)/passkey.Po
|
||||
-rm -f ./$(DEPDIR)/query.Po
|
||||
-rm -f ./$(DEPDIR)/queue.Po
|
||||
-rm -f ./$(DEPDIR)/queue_tests-ipa_otpd_queue_cmocka_tests.Po
|
||||
@@ -1195,10 +1242,12 @@ install-ps-am:
|
||||
installcheck-am:
|
||||
|
||||
maintainer-clean: maintainer-clean-am
|
||||
-rm -f ./$(DEPDIR)/bind.Po
|
||||
-rm -f ./$(DEPDIR)/bind.Po
|
||||
-rm -f ./$(DEPDIR)/forward.Po
|
||||
-rm -f ./$(DEPDIR)/main.Po
|
||||
-rm -f ./$(DEPDIR)/oauth2.Po
|
||||
-rm -f ./$(DEPDIR)/parse.Po
|
||||
-rm -f ./$(DEPDIR)/passkey.Po
|
||||
-rm -f ./$(DEPDIR)/query.Po
|
||||
-rm -f ./$(DEPDIR)/queue.Po
|
||||
-rm -f ./$(DEPDIR)/queue_tests-ipa_otpd_queue_cmocka_tests.Po
|
||||
@@ -1258,3 +1307,10 @@ uninstall-am: uninstall-appPROGRAMS uninstall-systemdsystemunitDATA
|
||||
# Tell versions [3.59,3.63) of GNU make to not export all variables.
|
||||
# Otherwise a system limit (for SysV at least) may be exceeded.
|
||||
.NOEXPORT:
|
||||
|
||||
# Tell GNU make to disable its built-in pattern rules.
|
||||
%:: %,v
|
||||
%:: RCS/%,v
|
||||
%:: RCS/%
|
||||
%:: s.%
|
||||
%:: SCCS/s.%
|
||||
|
||||
@@ -79,15 +79,18 @@ static void on_bind_readable(verto_ctx *vctx, verto_ev *ev)
|
||||
struct otpd_queue_item *item = NULL;
|
||||
int i, rslt;
|
||||
(void)vctx;
|
||||
int kerr = 0;
|
||||
|
||||
rslt = ldap_result(verto_get_private(ev), LDAP_RES_ANY, 0, NULL, &results);
|
||||
if (rslt != LDAP_RES_BIND) {
|
||||
if (rslt <= 0)
|
||||
results = NULL;
|
||||
ldap_msgfree(results);
|
||||
otpd_log_err(EIO, "IO error received on bind socket");
|
||||
otpd_log_err(EIO, "IO error received on bind socket: %s", ldap_err2string(rslt));
|
||||
verto_break(ctx.vctx);
|
||||
ctx.exitstatus = 1;
|
||||
/* if result is -1 or 0, connection was closed by the server side
|
||||
* or the server is down and we should exit gracefully */
|
||||
ctx.exitstatus = (rslt <= 0) ? 0 : 1;
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -116,6 +119,7 @@ static void on_bind_readable(verto_ctx *vctx, verto_ev *ev)
|
||||
krad_code_name2num("Access-Accept"),
|
||||
NULL, item->req, &item->rsp);
|
||||
if (i != 0) {
|
||||
kerr = 1;
|
||||
errstr = krb5_get_error_message(ctx.kctx, i);
|
||||
goto error;
|
||||
}
|
||||
@@ -125,6 +129,10 @@ error:
|
||||
otpd_log_req(item->req, "bind end: %s",
|
||||
item->rsp != NULL ? "success" : errstr);
|
||||
|
||||
if (kerr) {
|
||||
krb5_free_error_message(ctx.kctx, errstr);
|
||||
}
|
||||
|
||||
ldap_msgfree(results);
|
||||
otpd_queue_push(&ctx.stdio.responses, item);
|
||||
verto_set_flags(ctx.stdio.writer, VERTO_EV_FLAG_PERSIST |
|
||||
|
||||
@@ -43,10 +43,14 @@ static void forward_cb(krb5_error_code retval, const krad_packet *request,
|
||||
NULL, item->req, &item->rsp);
|
||||
}
|
||||
|
||||
otpd_log_req(item->req, "forward end: %s",
|
||||
retval == 0
|
||||
? krad_code_num2name(code)
|
||||
: krb5_get_error_message(ctx.kctx, retval));
|
||||
if (retval == 0) {
|
||||
otpd_log_req(item->req, "forward end: %s", krad_code_num2name(code));
|
||||
} else {
|
||||
const char *err_msg = krb5_get_error_message(ctx.kctx, retval);
|
||||
otpd_log_req(item->req, "forward end: %s",
|
||||
krb5_get_error_message(ctx.kctx, retval));
|
||||
krb5_free_error_message(ctx.kctx, err_msg);
|
||||
}
|
||||
|
||||
otpd_queue_push(&ctx.stdio.responses, item);
|
||||
verto_set_flags(ctx.stdio.writer, VERTO_EV_FLAG_PERSIST |
|
||||
@@ -117,8 +121,10 @@ krb5_error_code otpd_forward(struct otpd_queue_item **item)
|
||||
*item = NULL;
|
||||
|
||||
error:
|
||||
if (retval != 0)
|
||||
otpd_log_req((*item)->req, "forward end: %s",
|
||||
krb5_get_error_message(ctx.kctx, retval));
|
||||
if (retval != 0) {
|
||||
const char *err_msg = krb5_get_error_message(ctx.kctx, retval);
|
||||
otpd_log_req((*item)->req, "forward end: %s", err_msg);
|
||||
krb5_free_error_message(ctx.kctx, err_msg);
|
||||
}
|
||||
return retval;
|
||||
}
|
||||
|
||||
@@ -24,10 +24,19 @@
|
||||
|
||||
#include "krad.h"
|
||||
|
||||
#include <stdbool.h>
|
||||
|
||||
#include <ldap.h>
|
||||
|
||||
#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 +45,31 @@
|
||||
|
||||
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_PASSKEY,
|
||||
LDAP_QUERY_END
|
||||
};
|
||||
|
||||
enum oauth2_state {
|
||||
OAUTH2_NO = 0,
|
||||
OAUTH2_GET_ISSUER,
|
||||
OAUTH2_GET_DEVICE_CODE,
|
||||
OAUTH2_GET_ACCESS_TOKEN
|
||||
};
|
||||
|
||||
struct otpd_queue_item_passkey;
|
||||
|
||||
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 +77,10 @@ struct otpd_queue_item {
|
||||
char *uid;
|
||||
char *ipatokenRadiusUserName;
|
||||
char *ipatokenRadiusConfigLink;
|
||||
char *ipaidpSub;
|
||||
char *ipaidpConfigLink;
|
||||
char **ipaPassKey;
|
||||
char **ipauserauthtypes;
|
||||
char *other;
|
||||
} user;
|
||||
|
||||
@@ -58,6 +91,31 @@ 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;
|
||||
|
||||
bool get_passkey_config;
|
||||
struct otpd_queue_item_passkey *passkey;
|
||||
|
||||
int msgid;
|
||||
};
|
||||
|
||||
@@ -98,6 +156,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;
|
||||
@@ -108,9 +170,25 @@ void otpd_log_req_(const char * const file, int line, krad_packet *req,
|
||||
void otpd_log_err_(const char * const file, int line, krb5_error_code code,
|
||||
const char * const tmpl, ...);
|
||||
|
||||
int add_krad_attr_to_set(krad_packet *req, krad_attrset *attrset,
|
||||
krb5_data *datap, krad_attr attr, const char *message);
|
||||
|
||||
int get_krad_attr_from_packet(const krad_packet *rres,
|
||||
krad_attr attr, krb5_data *_data);
|
||||
|
||||
int get_string(LDAP *ldp, LDAPMessage *entry, const char *name,
|
||||
char **out);
|
||||
|
||||
int get_string_array(LDAP *ldp, LDAPMessage *entry, const char *name,
|
||||
char ***out);
|
||||
|
||||
bool auth_type_is(char **auth_types, const char *check);
|
||||
|
||||
krb5_error_code otpd_queue_item_new(krad_packet *req,
|
||||
struct otpd_queue_item **item);
|
||||
|
||||
void free_otpd_queue_item_passkey(struct otpd_queue_item *item);
|
||||
|
||||
void otpd_queue_item_free(struct otpd_queue_item *item);
|
||||
|
||||
krb5_error_code otpd_queue_iter_new(const struct otpd_queue * const *queues,
|
||||
@@ -143,8 +221,20 @@ 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);
|
||||
|
||||
const char *otpd_parse_passkey(LDAP *ldp, LDAPMessage *entry,
|
||||
struct otpd_queue_item *item);
|
||||
|
||||
bool is_passkey(struct otpd_queue_item *item);
|
||||
|
||||
int do_passkey(struct otpd_queue_item *item);
|
||||
|
||||
@@ -2,8 +2,9 @@
|
||||
Description=ipa-otpd service
|
||||
|
||||
[Service]
|
||||
Environment=LC_ALL=C.UTF-8
|
||||
EnvironmentFile=@sysconfdir@/ipa/default.conf
|
||||
ExecStart=@libexecdir@/ipa/ipa-otpd $ldap_uri
|
||||
StandardInput=socket
|
||||
StandardOutput=socket
|
||||
StandardError=syslog
|
||||
StandardError=journal
|
||||
|
||||
@@ -59,6 +59,12 @@ static void free_elts(struct otpd_queue *q)
|
||||
#define otpd_queue_item_free free_elt
|
||||
#define otpd_queue_free_items free_elts
|
||||
|
||||
void free_otpd_queue_item_passkey(struct otpd_queue_item *item)
|
||||
{
|
||||
(void)item; /* Unused */
|
||||
return;
|
||||
}
|
||||
|
||||
static void assert_elt_equal(struct otpd_queue_item *e1,
|
||||
struct otpd_queue_item *e2)
|
||||
{
|
||||
@@ -69,7 +75,7 @@ static void assert_elt_equal(struct otpd_queue_item *e1,
|
||||
assert_int_equal(e1->msgid, e2->msgid);
|
||||
}
|
||||
|
||||
static void test_single_insert()
|
||||
static void test_single_insert(void **state)
|
||||
{
|
||||
struct otpd_queue q = { NULL };
|
||||
struct otpd_queue_item *ein, *eout;
|
||||
@@ -90,7 +96,7 @@ static void test_single_insert()
|
||||
free_elts(&q);
|
||||
}
|
||||
|
||||
static void test_jump_insert()
|
||||
static void test_jump_insert(void **state)
|
||||
{
|
||||
struct otpd_queue q = { NULL };
|
||||
struct otpd_queue_item *echeck;
|
||||
@@ -106,7 +112,7 @@ static void test_jump_insert()
|
||||
free_elts(&q);
|
||||
}
|
||||
|
||||
static void test_garbage_insert()
|
||||
static void test_garbage_insert(void **state)
|
||||
{
|
||||
struct otpd_queue q = { NULL };
|
||||
struct otpd_queue_item *e, *g;
|
||||
@@ -121,7 +127,7 @@ static void test_garbage_insert()
|
||||
free_elts(&q);
|
||||
}
|
||||
|
||||
static void test_removal()
|
||||
static void test_removal(void **state)
|
||||
{
|
||||
struct otpd_queue q = { NULL };
|
||||
|
||||
@@ -149,7 +155,7 @@ static void pick_id(struct otpd_queue *q, int msgid)
|
||||
e = otpd_queue_pop_msgid(q, msgid);
|
||||
assert_ptr_equal(e, NULL);
|
||||
}
|
||||
static void test_pick_removal()
|
||||
static void test_pick_removal(void **state)
|
||||
{
|
||||
struct otpd_queue q = { NULL };
|
||||
|
||||
@@ -166,7 +172,7 @@ static void test_pick_removal()
|
||||
free_elts(&q);
|
||||
}
|
||||
|
||||
static void test_iter()
|
||||
static void test_iter(void **state)
|
||||
{
|
||||
krb5_error_code ret;
|
||||
struct otpd_queue q = { NULL };
|
||||
|
||||
@@ -32,6 +32,7 @@
|
||||
|
||||
#include <signal.h>
|
||||
#include <stdbool.h>
|
||||
#include "ipa_hostname.h"
|
||||
|
||||
/* Our global state. */
|
||||
struct otpd_context ctx;
|
||||
@@ -87,6 +88,80 @@ void otpd_log_err_(const char * const file, int line, krb5_error_code code,
|
||||
fprintf(stderr, "\n");
|
||||
}
|
||||
|
||||
#define min(a,b) ((a) > (b) ? (b) : (a))
|
||||
int add_krad_attr_to_set(krad_packet *req, 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(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. */
|
||||
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;
|
||||
}
|
||||
|
||||
static void on_ldap_free(verto_ctx *vctx, verto_ev *ev)
|
||||
{
|
||||
(void)vctx; /* Unused */
|
||||
@@ -212,7 +287,8 @@ static krb5_error_code setup_ldap(const char *uri, krb5_boolean bind,
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
char hostname[HOST_NAME_MAX + 1];
|
||||
const char *hostname;
|
||||
char fqdn[IPA_HOST_FQDN_LEN + 1];
|
||||
krb5_error_code retval;
|
||||
krb5_data hndata;
|
||||
verto_ev *sig;
|
||||
@@ -227,10 +303,12 @@ int main(int argc, char **argv)
|
||||
memset(&ctx, 0, sizeof(ctx));
|
||||
ctx.exitstatus = 1;
|
||||
|
||||
if (gethostname(hostname, sizeof(hostname)) < 0) {
|
||||
hostname = ipa_gethostfqdn();
|
||||
if (hostname == NULL) {
|
||||
otpd_log_err(errno, "Unable to get hostname");
|
||||
goto error;
|
||||
}
|
||||
strncpy(fqdn, hostname, IPA_HOST_FQDN_LEN);
|
||||
|
||||
retval = krb5_init_context(&ctx.kctx);
|
||||
if (retval != 0) {
|
||||
@@ -238,7 +316,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;
|
||||
@@ -252,7 +331,7 @@ int main(int argc, char **argv)
|
||||
}
|
||||
|
||||
/* Set NAS-Identifier. */
|
||||
hndata.data = hostname;
|
||||
hndata.data = fqdn;
|
||||
hndata.length = strlen(hndata.data);
|
||||
retval = krad_attrset_add(ctx.attrs, krad_attr_name2num("NAS-Identifier"),
|
||||
&hndata);
|
||||
@@ -338,4 +417,3 @@ error:
|
||||
krb5_free_context(ctx.kctx);
|
||||
return ctx.exitstatus;
|
||||
}
|
||||
|
||||
|
||||
545
daemons/ipa-otpd/oauth2.c
Normal file
545
daemons/ipa-otpd/oauth2.c
Normal file
@@ -0,0 +1,545 @@
|
||||
/*
|
||||
* 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 <sys/uio.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;
|
||||
struct iovec iov[3];
|
||||
|
||||
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) {
|
||||
io = write(verto_get_fd(ev), child_ctx->item->idp.ipaidpClientSecret,
|
||||
strlen(child_ctx->item->idp.ipaidpClientSecret));
|
||||
} else {
|
||||
iov[0].iov_base = child_ctx->item->idp.ipaidpClientSecret;
|
||||
iov[0].iov_len = strlen(child_ctx->item->idp.ipaidpClientSecret);
|
||||
iov[1].iov_base = "\n";
|
||||
iov[1].iov_len = 1;
|
||||
iov[2].iov_base = child_ctx->saved_item->oauth2.device_code_reply;
|
||||
iov[2].iov_len = strlen(child_ctx->saved_item->oauth2.device_code_reply);
|
||||
|
||||
io = writev(verto_get_fd(ev), iov, 3);
|
||||
}
|
||||
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);
|
||||
}
|
||||
|
||||
/* 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 = NULL;
|
||||
|
||||
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->item->req,
|
||||
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->item->req, 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 *vctx, verto_ev *ev)
|
||||
{
|
||||
static char buf[10240];
|
||||
ssize_t io = 0;
|
||||
struct child_ctx *child_ctx = NULL;
|
||||
int ret;
|
||||
char *rad_reply;
|
||||
char *end;
|
||||
|
||||
(void) vctx; /* Unused */
|
||||
|
||||
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 = NULL;
|
||||
|
||||
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-stdin";
|
||||
}
|
||||
|
||||
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,15 +24,18 @@
|
||||
* 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
|
||||
|
||||
/* Convert an LDAP entry into an allocated string. */
|
||||
static int get_string(LDAP *ldp, LDAPMessage *entry, const char *name,
|
||||
char **out)
|
||||
int get_string(LDAP *ldp, LDAPMessage *entry, const char *name,
|
||||
char **out)
|
||||
{
|
||||
struct berval **vals;
|
||||
ber_len_t i;
|
||||
@@ -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. */
|
||||
int get_string_array(LDAP *ldp, LDAPMessage *entry, const char *name,
|
||||
char ***out)
|
||||
{
|
||||
struct berval **vals;
|
||||
ber_len_t i;
|
||||
char **buf = NULL;
|
||||
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,26 @@ 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, "ipaPassKey",
|
||||
&item->user.ipaPassKey);
|
||||
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 +208,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)
|
||||
|
||||
824
daemons/ipa-otpd/passkey.c
Normal file
824
daemons/ipa-otpd/passkey.c
Normal file
@@ -0,0 +1,824 @@
|
||||
/*
|
||||
* FreeIPA 2FA companion daemon
|
||||
*
|
||||
* Authors: Sumit Bose <sbose@redhat.com>
|
||||
*
|
||||
* Copyright (C) 2022 Sumit Bose, 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 contains various helper functions for the passkey feature.
|
||||
*/
|
||||
|
||||
#define _GNU_SOURCE /* for asprintf() */
|
||||
#include <stdio.h>
|
||||
#include <fcntl.h>
|
||||
#include <jansson.h>
|
||||
#include <openssl/rand.h>
|
||||
#include <openssl/evp.h>
|
||||
|
||||
#include "internal.h"
|
||||
|
||||
struct passkey_data {
|
||||
int phase;
|
||||
char *state;
|
||||
union {
|
||||
struct passkey_challenge {
|
||||
char *domain;
|
||||
json_t *credential_id_list;
|
||||
int user_verification;
|
||||
unsigned char *cryptographic_challenge;
|
||||
} challenge;
|
||||
|
||||
struct sss_passkey_reply {
|
||||
char *credential_id;
|
||||
char *cryptographic_challenge;
|
||||
char *authenticator_data;
|
||||
char *assertion_signature;
|
||||
char *user_id;
|
||||
} response;
|
||||
} data;
|
||||
json_t *jdata;
|
||||
json_t *jroot;
|
||||
};
|
||||
|
||||
struct otpd_queue_item_passkey {
|
||||
char *domain;
|
||||
char *ipaRequireUserVerification;
|
||||
struct passkey_data *data_in;
|
||||
struct passkey_data *data_out;
|
||||
krb5_data state;
|
||||
char* ipapasskeyDebugLevelStr;
|
||||
krb5_boolean ipapasskeyDebugFido2;
|
||||
};
|
||||
|
||||
static void free_passkey_data(struct passkey_data *p)
|
||||
{
|
||||
if (p == NULL) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (p->phase == 1) {
|
||||
free(p->data.challenge.domain);
|
||||
free(p->data.challenge.cryptographic_challenge);
|
||||
}
|
||||
|
||||
json_decref(p->jdata);
|
||||
json_decref(p->jroot);
|
||||
free(p);
|
||||
}
|
||||
|
||||
void free_otpd_queue_item_passkey(struct otpd_queue_item *item)
|
||||
{
|
||||
if (item == NULL || item->passkey == NULL) {
|
||||
return;
|
||||
}
|
||||
|
||||
free(item->passkey->domain);
|
||||
free(item->passkey->ipaRequireUserVerification);
|
||||
|
||||
free_passkey_data(item->passkey->data_in);
|
||||
free_passkey_data(item->passkey->data_out);
|
||||
|
||||
free(item->passkey);
|
||||
}
|
||||
|
||||
static struct otpd_queue_item_passkey *get_otpd_queue_item_passkey(void)
|
||||
{
|
||||
struct otpd_queue_item_passkey *p;
|
||||
|
||||
p = calloc(1, sizeof(struct otpd_queue_item_passkey));
|
||||
if (p == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
p->data_in = calloc(1, sizeof(struct passkey_data));
|
||||
if (p->data_in == NULL) {
|
||||
free(p);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
p->data_out = calloc(1, sizeof(struct passkey_data));
|
||||
if (p->data_out == NULL) {
|
||||
free(p->data_in);
|
||||
free(p);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
p->data_in->phase = -1;
|
||||
p->data_out->phase = -1;
|
||||
|
||||
return p;
|
||||
}
|
||||
|
||||
#define PASSKEY_PREFIX "passkey "
|
||||
#define ENV_PASSKEY_CHILD_DEBUG_LEVEL "passkey_child_debug_level"
|
||||
|
||||
/* Parse the passkey configuration */
|
||||
const char *otpd_parse_passkey(LDAP *ldp, LDAPMessage *entry,
|
||||
struct otpd_queue_item *item)
|
||||
{
|
||||
int i;
|
||||
char **objectclasses = NULL;
|
||||
long dbg_lvl = 0;
|
||||
const char *dbg_env = NULL;
|
||||
char *endptr = NULL;
|
||||
|
||||
if (item->passkey == NULL) {
|
||||
otpd_log_req(item->req,
|
||||
"Missing passkey struct to store passkey configuration");
|
||||
return strerror(EINVAL);
|
||||
}
|
||||
|
||||
while (entry != NULL) {
|
||||
i = get_string_array(ldp, entry, "objectclass", &objectclasses);
|
||||
if (i != 0) {
|
||||
return strerror(i);
|
||||
}
|
||||
|
||||
if (auth_type_is(objectclasses, "ipapasskeyconfigobject")) {
|
||||
free(objectclasses);
|
||||
|
||||
i = get_string(ldp, entry, "ipaRequireUserVerification",
|
||||
&item->passkey->ipaRequireUserVerification);
|
||||
if ((i != 0) && (i != ENOENT)) {
|
||||
return strerror(i);
|
||||
}
|
||||
} else if (auth_type_is(objectclasses, "domainRelatedObject")) {
|
||||
free(objectclasses);
|
||||
|
||||
i = get_string(ldp, entry, "associatedDomain",
|
||||
&item->passkey->domain);
|
||||
if ((i != 0) && (i != ENOENT)) {
|
||||
return strerror(i);
|
||||
}
|
||||
}
|
||||
|
||||
entry = ldap_next_entry(ldp, entry);
|
||||
};
|
||||
|
||||
item->passkey->ipapasskeyDebugLevelStr = NULL;
|
||||
item->passkey->ipapasskeyDebugFido2 = FALSE;
|
||||
dbg_env = getenv(ENV_PASSKEY_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->passkey->ipapasskeyDebugLevelStr, "%ld",
|
||||
dbg_lvl) != -1) {
|
||||
if (dbg_lvl > 5) {
|
||||
item->passkey->ipapasskeyDebugFido2 = 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_PASSKEY_CHILD_DEBUG_LEVEL);
|
||||
}
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static int decode_json(const char *inp, size_t size, struct passkey_data *data)
|
||||
{
|
||||
json_error_t jret;
|
||||
int ret;
|
||||
|
||||
data->jroot = json_loadb(inp, size, 0, &jret);
|
||||
if (data->jroot == NULL) {
|
||||
return EINVAL;
|
||||
}
|
||||
data->jdata = NULL;
|
||||
data->phase = -1;
|
||||
|
||||
ret = json_unpack(data->jroot, "{s:i, s?:s, s?:o}",
|
||||
"phase", &data->phase,
|
||||
"state", &data->state,
|
||||
"data", &data->jdata);
|
||||
if (ret != 0) {
|
||||
ret = EINVAL;
|
||||
goto done;
|
||||
}
|
||||
|
||||
switch (data->phase) {
|
||||
case 0: /* SSS_PASSKEY_PHASE_INIT */
|
||||
/* no data */
|
||||
if (data->jdata != NULL) {
|
||||
ret = EINVAL;
|
||||
} else {
|
||||
ret = 0;
|
||||
}
|
||||
break;
|
||||
case 2: /* SSS_PASSKEY_PHASE_REPLY */
|
||||
ret = json_unpack(data->jdata, "{s:s, s:s, s:s, s:s}",
|
||||
"credential_id", &data->data.response.credential_id,
|
||||
"cryptographic_challenge", &data->data.response.cryptographic_challenge,
|
||||
"authenticator_data", &data->data.response.authenticator_data,
|
||||
"assertion_signature", &data->data.response.assertion_signature,
|
||||
"user_id", &data->data.response.user_id);
|
||||
break;
|
||||
default:
|
||||
ret = EINVAL;
|
||||
}
|
||||
|
||||
done:
|
||||
if (ret != 0) {
|
||||
json_decref(data->jdata);
|
||||
data->jdata = NULL;
|
||||
json_decref(data->jroot);
|
||||
data->jroot = NULL;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int passkey_parse_data(const char *data, size_t size, struct otpd_queue_item *item)
|
||||
{
|
||||
item->passkey = get_otpd_queue_item_passkey();
|
||||
if (item->passkey == NULL) {
|
||||
return ENOMEM;
|
||||
}
|
||||
|
||||
return decode_json(data, size, item->passkey->data_in);
|
||||
}
|
||||
|
||||
bool is_passkey(struct otpd_queue_item *item)
|
||||
{
|
||||
const krb5_data *data_pwd;
|
||||
krb5_data data_state = { 0 };
|
||||
int ret;
|
||||
|
||||
if (item->passkey != NULL) {
|
||||
return true;
|
||||
}
|
||||
|
||||
data_pwd = krad_packet_get_attr(item->req,
|
||||
krad_attr_name2num("User-Password"), 0);
|
||||
ret = get_krad_attr_from_packet(item->req,
|
||||
krad_attr_name2num("Proxy-State"),
|
||||
&data_state);
|
||||
|
||||
if (data_pwd == NULL && ret == 0
|
||||
&& data_state.length > strlen(PASSKEY_PREFIX)
|
||||
&& strncmp(data_state.data, PASSKEY_PREFIX,
|
||||
strlen(PASSKEY_PREFIX)) == 0
|
||||
&& (item->user.ipauserauthtypes == NULL
|
||||
|| item->user.ipauserauthtypes[0] == NULL
|
||||
|| *(item->user.ipauserauthtypes[0]) == '\0'
|
||||
|| auth_type_is(item->user.ipauserauthtypes, "passkey"))) {
|
||||
|
||||
ret = passkey_parse_data(data_state.data + strlen(PASSKEY_PREFIX),
|
||||
data_state.length - strlen(PASSKEY_PREFIX) - 1,
|
||||
item);
|
||||
krb5_free_data_contents(NULL, &data_state);
|
||||
if (ret != 0) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
#define PK_PREF "passkey:"
|
||||
|
||||
static json_t *ipa_passkey_to_json_array(char **ipa_passkey)
|
||||
{
|
||||
int ret;
|
||||
const char *sep;
|
||||
char *start;
|
||||
size_t c;
|
||||
json_t *ja = NULL;
|
||||
json_t *js;
|
||||
|
||||
if (ipa_passkey == NULL || *ipa_passkey == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
ja = json_array();
|
||||
if (ja == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
for (c = 0; ipa_passkey[c] != NULL; c++) {
|
||||
if (strncmp(ipa_passkey[c], PK_PREF, strlen(PK_PREF)) != 0) {
|
||||
otpd_log_err(ret, "Missing prefix in [%s]", ipa_passkey[c]);
|
||||
continue;
|
||||
}
|
||||
start = ipa_passkey[c] + strlen(PK_PREF);
|
||||
sep = strchr(start, ',');
|
||||
if (sep == NULL || sep == start) {
|
||||
otpd_log_err(ret, "Missing seperator in [%s]", ipa_passkey[c]);
|
||||
continue;
|
||||
}
|
||||
|
||||
js = json_stringn(start, sep - start);
|
||||
if (js == NULL) {
|
||||
ret = ENOMEM;
|
||||
goto done;
|
||||
}
|
||||
|
||||
ret = json_array_append_new(ja, js);
|
||||
if (ret != 0) {
|
||||
goto done;
|
||||
}
|
||||
}
|
||||
|
||||
done:
|
||||
if (ret != 0) {
|
||||
json_decref(ja);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return ja;
|
||||
}
|
||||
|
||||
/* passkey string:
|
||||
* key_handle,public_key(,optional_user_id)
|
||||
*/
|
||||
static char *ipa_passkey_get_public_key(char **ipa_passkey, const char *key_id)
|
||||
{
|
||||
char *sep;
|
||||
char *sep2;
|
||||
size_t c;
|
||||
char *start;
|
||||
|
||||
if (ipa_passkey == NULL || *ipa_passkey == NULL
|
||||
|| key_id == NULL || *key_id == '\0') {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
for (c = 0; ipa_passkey[c] != NULL; c++) {
|
||||
if (strncmp(ipa_passkey[c], PK_PREF, strlen(PK_PREF)) != 0) {
|
||||
otpd_log_err(EINVAL, "Missing prefix in [%s]", ipa_passkey[c]);
|
||||
continue;
|
||||
}
|
||||
start = ipa_passkey[c] + strlen(PK_PREF);
|
||||
|
||||
sep = strchr(start, ',');
|
||||
if (sep == NULL || sep == start) {
|
||||
otpd_log_err(EINVAL, "Missing seperator in [%s]", ipa_passkey[c]);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (strncmp(start, key_id, sep - start) == 0) {
|
||||
sep2 = strchrnul(sep + 1, ',');
|
||||
if (sep2 == sep + 1) {
|
||||
return NULL;
|
||||
}
|
||||
*sep2 = '\0';
|
||||
return (sep + 1);
|
||||
}
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
#define CHALLENGE_LENGTH 32
|
||||
static unsigned char *get_b64_challenge(void)
|
||||
{
|
||||
int ret;
|
||||
unsigned char buf[CHALLENGE_LENGTH];
|
||||
unsigned char *b64;
|
||||
|
||||
ret = RAND_bytes(buf, CHALLENGE_LENGTH);
|
||||
if (ret != 1) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
b64 = calloc(1, 2 * CHALLENGE_LENGTH);
|
||||
if (b64 == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
ret = EVP_EncodeBlock(b64, buf, CHALLENGE_LENGTH);
|
||||
if (ret == 0) {
|
||||
free(b64);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return b64;
|
||||
}
|
||||
|
||||
static int prepare_rad_reply(struct otpd_queue_item *item)
|
||||
{
|
||||
krad_attrset *attrset = NULL;
|
||||
int ret;
|
||||
json_t *jtmp = NULL;
|
||||
char *stmp = NULL;
|
||||
krb5_data data = { 0 };
|
||||
|
||||
ret = krad_attrset_new(ctx.kctx, &attrset);
|
||||
if (ret != 0) {
|
||||
otpd_log_err(ret, "Failed to create radius attribute set");
|
||||
goto done;
|
||||
}
|
||||
|
||||
jtmp = json_pack("{s:i, s:s, s:o}", "phase", item->passkey->data_out->phase,
|
||||
"state", item->passkey->data_out->state,
|
||||
"data", item->passkey->data_out->jdata);
|
||||
if (jtmp == NULL) {
|
||||
ret = EIO;
|
||||
otpd_log_err(ret, "Failed to pack JSON reply");
|
||||
goto done;
|
||||
}
|
||||
|
||||
stmp = json_dumps(jtmp, JSON_COMPACT);
|
||||
if (stmp == NULL) {
|
||||
ret = EIO;
|
||||
otpd_log_err(ret, "Failed to dump JSON string");
|
||||
goto done;
|
||||
}
|
||||
|
||||
ret = asprintf(&(data.data), "passkey %s", stmp);
|
||||
if (ret < 0) {
|
||||
ret = ENOMEM;
|
||||
otpd_log_err(ret, "Failed to generate reply string");
|
||||
goto done;
|
||||
}
|
||||
data.length = strlen(data.data);
|
||||
data.magic = 0;
|
||||
|
||||
|
||||
ret = add_krad_attr_to_set(item->req, attrset, &data,
|
||||
krad_attr_name2num("Proxy-State"),
|
||||
"Failed to serialize state to attribute set");
|
||||
if (ret != 0) {
|
||||
otpd_log_err(ret, "Failed to add Proxy-State");
|
||||
goto done;
|
||||
}
|
||||
|
||||
ret = krad_packet_new_response(ctx.kctx, SECRET,
|
||||
krad_code_name2num("Access-Challenge"),
|
||||
attrset,
|
||||
item->req, &item->rsp);
|
||||
if (ret != 0) {
|
||||
otpd_log_err(ret, "Failed to create radius response");
|
||||
item->rsp = NULL;
|
||||
}
|
||||
|
||||
ret = 0;
|
||||
|
||||
done:
|
||||
krad_attrset_free(attrset);
|
||||
free(stmp);
|
||||
json_decref(jtmp);
|
||||
|
||||
if (ret != 0) {
|
||||
free(data.data);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int do_passkey_challenge(struct otpd_queue_item *item)
|
||||
{
|
||||
unsigned char *challenge = NULL;
|
||||
int ret;
|
||||
struct passkey_data *d;
|
||||
|
||||
d = item->passkey->data_out;
|
||||
|
||||
d->data.challenge.credential_id_list = ipa_passkey_to_json_array(
|
||||
item->user.ipaPassKey);
|
||||
if (d->data.challenge.credential_id_list == NULL) {
|
||||
return EINVAL;
|
||||
}
|
||||
|
||||
/* Secure by default, assume user verification is enabled and disable it
|
||||
* only if the option is set to 'false'. */
|
||||
d->data.challenge.user_verification = 1;
|
||||
if (item->passkey->ipaRequireUserVerification != NULL
|
||||
&& strcasecmp(item->passkey->ipaRequireUserVerification,
|
||||
"false") == 0) {
|
||||
d->data.challenge.user_verification = 0;
|
||||
}
|
||||
|
||||
d->data.challenge.cryptographic_challenge = get_b64_challenge();
|
||||
if (d->data.challenge.cryptographic_challenge == NULL) {
|
||||
ret = ENOMEM;
|
||||
goto done;
|
||||
}
|
||||
|
||||
d->jdata = json_pack("{s:s, s:o, s:i, s:s}",
|
||||
"domain", item->passkey->domain,
|
||||
"credential_id_list",
|
||||
d->data.challenge.credential_id_list,
|
||||
"user_verification",
|
||||
d->data.challenge.user_verification,
|
||||
"cryptographic_challenge",
|
||||
d->data.challenge.cryptographic_challenge);
|
||||
if (d->jdata == NULL) {
|
||||
ret = EIO;
|
||||
goto done;
|
||||
}
|
||||
|
||||
d->phase = 1; /* SSS_PASSKEY_PHASE_CHALLENGE */
|
||||
d->state = strdup("ipa_otpd state");
|
||||
|
||||
ret = prepare_rad_reply(item);
|
||||
if (ret != 0) {
|
||||
otpd_log_err(ret, "prepare_rad_reply() failed.");
|
||||
goto done;
|
||||
}
|
||||
|
||||
ret = 0;
|
||||
done:
|
||||
free(challenge);
|
||||
|
||||
otpd_queue_push(&ctx.stdio.responses, 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);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
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;
|
||||
};
|
||||
|
||||
static void passkey_on_child_writable(verto_ctx *vctx, verto_ev *ev)
|
||||
{
|
||||
(void)vctx; /* Unused */
|
||||
|
||||
/* no input needed */
|
||||
verto_del(ev);
|
||||
return;
|
||||
}
|
||||
|
||||
static void passkey_on_child_readable(verto_ctx *vctx, verto_ev *ev)
|
||||
{
|
||||
(void)vctx; /* Unused */
|
||||
|
||||
/* no output expected */
|
||||
verto_del(ev);
|
||||
return;
|
||||
}
|
||||
|
||||
static void passkey_on_child_exit(verto_ctx *vctx, verto_ev *ev)
|
||||
{
|
||||
(void)vctx; /* Unused */
|
||||
int ret;
|
||||
verto_proc_status st;
|
||||
struct child_ctx *child_ctx = NULL;
|
||||
|
||||
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;
|
||||
|
||||
st = verto_get_proc_status(ev);
|
||||
|
||||
if (!WIFEXITED(st)) {
|
||||
otpd_log_err(0, "Child didn't exit normally.");
|
||||
verto_del(ev);
|
||||
goto done;
|
||||
}
|
||||
|
||||
/* 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));
|
||||
|
||||
verto_del(ev);
|
||||
|
||||
if (WEXITSTATUS(st) != 0) {
|
||||
/* verification failed */
|
||||
goto done;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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 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 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;
|
||||
}
|
||||
|
||||
#ifndef PASSKEY_CHILD_PATH
|
||||
#define PASSKEY_CHILD_PATH "/usr/libexec/sssd/passkey_child"
|
||||
#endif
|
||||
|
||||
static int do_passkey_response(struct otpd_queue_item *item)
|
||||
{
|
||||
int ret;
|
||||
pid_t child_pid;
|
||||
int pipefd_to_child[2] = { -1, -1};
|
||||
int pipefd_from_child[2] = { -1, -1};
|
||||
/* 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;
|
||||
struct child_ctx *child_ctx;
|
||||
char *pk = NULL;
|
||||
|
||||
child_ctx = calloc(sizeof(struct child_ctx), 1);
|
||||
if (child_ctx == NULL) {
|
||||
ret = ENOMEM;
|
||||
goto done;
|
||||
}
|
||||
child_ctx->item = item;
|
||||
|
||||
pk = ipa_passkey_get_public_key(item->user.ipaPassKey,
|
||||
item->passkey->data_in->data.response.credential_id);
|
||||
if (pk == NULL) {
|
||||
ret = EINVAL;
|
||||
otpd_log_err(ret, "No matching public key found for [%s]",
|
||||
item->passkey->data_in->data.response.credential_id);
|
||||
goto done;
|
||||
}
|
||||
|
||||
args[args_idx++] = PASSKEY_CHILD_PATH;
|
||||
args[args_idx++] = "--verify-assert";
|
||||
args[args_idx++] = "--domain";
|
||||
args[args_idx++] = item->passkey->domain;
|
||||
args[args_idx++] = "--key-handle";
|
||||
args[args_idx++] = item->passkey->data_in->data.response.credential_id;
|
||||
args[args_idx++] = "--public-key";
|
||||
args[args_idx++] = pk;
|
||||
args[args_idx++] = "--cryptographic-challenge";
|
||||
args[args_idx++] = item->passkey->data_in->data.response.cryptographic_challenge;
|
||||
args[args_idx++] = "--auth-data";
|
||||
args[args_idx++] = item->passkey->data_in->data.response.authenticator_data;
|
||||
args[args_idx++] = "--signature";
|
||||
args[args_idx++] = item->passkey->data_in->data.response.assertion_signature;
|
||||
if (item->passkey->ipapasskeyDebugLevelStr != NULL) {
|
||||
args[args_idx++] = "--debug-level";
|
||||
args[args_idx++] = item->passkey->ipapasskeyDebugLevelStr;
|
||||
}
|
||||
if (item->passkey->ipapasskeyDebugFido2) {
|
||||
args[args_idx++] = "--debug-libfido2";
|
||||
}
|
||||
|
||||
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(args[0], 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,
|
||||
passkey_on_child_writable,
|
||||
child_ctx->write_to_child);
|
||||
if (child_ctx->write_ev == NULL) {
|
||||
ret = ENOMEM;
|
||||
otpd_log_err(ret, "Unable to initialize passkey 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,
|
||||
passkey_on_child_readable,
|
||||
child_ctx->read_from_child);
|
||||
if (child_ctx->read_ev == NULL) {
|
||||
ret = ENOMEM;
|
||||
otpd_log_err(ret, "Unable to initialize passkey reader 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,
|
||||
passkey_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 passkey_child");
|
||||
goto done;
|
||||
}
|
||||
|
||||
ret = 0;
|
||||
|
||||
done:
|
||||
if (ret != 0) {
|
||||
free(child_ctx);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
int do_passkey(struct otpd_queue_item *item)
|
||||
{
|
||||
if (item == NULL || item->passkey == NULL
|
||||
|| item->passkey->data_in == NULL) {
|
||||
return EINVAL;
|
||||
}
|
||||
|
||||
switch (item->passkey->data_in->phase) {
|
||||
case 0: /* SSS_PASSKEY_PHASE_INIT */
|
||||
return do_passkey_challenge(item);
|
||||
case 2: /* SSS_PASSKEY_PHASE_REPLY */
|
||||
return do_passkey_response(item);
|
||||
default:
|
||||
return EINVAL;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -31,14 +31,29 @@
|
||||
#define _GNU_SOURCE 1 /* for asprintf() */
|
||||
#include "internal.h"
|
||||
#include <ctype.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
#define DEFAULT_TIMEOUT 15
|
||||
#define DEFAULT_RETRIES 3
|
||||
|
||||
/* To read passkey configuration and attributes from a different server than
|
||||
* FreeIPA you might have to the following two defines of the search filter
|
||||
* for the global configuration data and the attribute name where if passkey
|
||||
* information is stored in the user entry. Additionally otpd_parse_passkey()
|
||||
* might need some updates depending on how the global configuration is stored
|
||||
* in the configuration objects.
|
||||
*/
|
||||
#define PASSKEY_CONFIG_FILTER "(|(objectclass=ipapasskeyconfigobject)(&(objectclass=domain)(objectclass=domainRelatedObject)))"
|
||||
#define PASSKEY_USER_ATTR "ipapasskey"
|
||||
|
||||
static char *user[] = {
|
||||
"uid",
|
||||
"ipatokenRadiusUserName",
|
||||
"ipatokenRadiusConfigLink",
|
||||
"ipaidpSub",
|
||||
"ipaidpConfigLink",
|
||||
"ipauserauthtype",
|
||||
PASSKEY_USER_ATTR,
|
||||
NULL
|
||||
};
|
||||
|
||||
@@ -51,6 +66,37 @@ static char *radius[] = {
|
||||
NULL
|
||||
};
|
||||
|
||||
static char *idp[] = {
|
||||
"ipaidpClientID",
|
||||
"ipaidpClientSecret",
|
||||
"ipaidpIssuerURL",
|
||||
"ipaidpDevAuthEndpoint",
|
||||
"ipaidpTokenEndpoint",
|
||||
"ipaidpUserInfoEndpoint",
|
||||
"ipaidpKeysEndpoint",
|
||||
"ipaidpScope",
|
||||
"ipaidpSub",
|
||||
"cn",
|
||||
NULL
|
||||
};
|
||||
|
||||
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 +122,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 +133,28 @@ static void on_query_writable(verto_ctx *vctx, verto_ev *ev)
|
||||
NULL, NULL, 1, &item->msgid);
|
||||
free(filter);
|
||||
|
||||
} else if (item->get_passkey_config) {
|
||||
otpd_log_req(item->req, "passkey config query start:");
|
||||
item->ldap_query = LDAP_QUERY_PASSKEY;
|
||||
|
||||
i = ldap_search_ext(verto_get_private(ev), ctx.query.base,
|
||||
LDAP_SCOPE_SUBTREE, PASSKEY_CONFIG_FILTER, NULL, 0, NULL,
|
||||
NULL, NULL, 0, &item->msgid);
|
||||
|
||||
} 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 +164,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 +174,6 @@ static void on_query_writable(verto_ctx *vctx, verto_ev *ev)
|
||||
}
|
||||
|
||||
if (i == LDAP_SUCCESS) {
|
||||
item->sent++;
|
||||
push = &ctx.query.responses;
|
||||
}
|
||||
|
||||
@@ -115,6 +181,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 +264,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 +289,22 @@ 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;
|
||||
case LDAP_QUERY_PASSKEY:
|
||||
err = otpd_parse_passkey(ldp, entry, item);
|
||||
break;
|
||||
default:
|
||||
ldap_msgfree(entry);
|
||||
goto egress;
|
||||
@@ -181,14 +326,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 +342,72 @@ 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;
|
||||
case LDAP_QUERY_PASSKEY:
|
||||
otpd_log_req(item->req, "passkey query end: %s",
|
||||
item->error == NULL ? "ok" : item->error);
|
||||
if (item->passkey == NULL) {
|
||||
goto egress;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
goto egress;
|
||||
}
|
||||
|
||||
if (item->error != NULL)
|
||||
goto egress;
|
||||
/* Check for passkey */
|
||||
if (is_passkey(item)) {
|
||||
if (item->ldap_query == LDAP_QUERY_USER) {
|
||||
item->get_passkey_config = true;
|
||||
|
||||
if (item->sent == 1 && item->user.ipatokenRadiusConfigLink != NULL) {
|
||||
push = &ctx.query.requests;
|
||||
event = ctx.query.io;
|
||||
goto egress;
|
||||
}
|
||||
|
||||
i = do_passkey(item);
|
||||
if (i != 0) {
|
||||
goto egress;
|
||||
}
|
||||
/* do_passkey will call ctx.stdio.writer, so we can return here */
|
||||
return;
|
||||
}
|
||||
|
||||
/* 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,12 +56,40 @@ 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);
|
||||
|
||||
free_otpd_queue_item_passkey(item);
|
||||
|
||||
free(item);
|
||||
}
|
||||
|
||||
|
||||
@@ -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)));
|
||||
|
||||
@@ -59,7 +59,7 @@ def main():
|
||||
rsp.DecodePacket(buf)
|
||||
pkt.VerifyReply(rsp)
|
||||
|
||||
proc.terminate() # pylint: disable=E1101
|
||||
proc.terminate()
|
||||
proc.wait()
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
||||
Reference in New Issue
Block a user