diff --git a/.gitlab-ci-scripts/local-before-script.sh b/.gitlab-ci-scripts/local-before-script.sh index d5df138f..b5246ac4 100755 --- a/.gitlab-ci-scripts/local-before-script.sh +++ b/.gitlab-ci-scripts/local-before-script.sh @@ -7,14 +7,16 @@ echo "======== oidc-agent-local-before-script starting=======" export VERSION=`cat VERSION` # clone the packages file of this repo: # Try with VERSION +echo "Trying to use branch for packaging: ${PACKAGING_BRANCH}/v${VERSION}" git clone -b ${PACKAGING_BRANCH}/v${VERSION} http://git.scc.kit.edu/m-team/oidc-agent.git delme || { + echo "Falling back to ${PACKAGING_BRANCH}/latest" git clone -b ${PACKAGING_BRANCH}/latest http://git.scc.kit.edu/m-team/oidc-agent.git delme } case ${DISTRO} in debian|ubuntu) - ls -la + # ls -la [ -d debian ] && { echo "There IS an existing debian folder" echo "This is the content:" @@ -41,33 +43,44 @@ case ${DISTRO} in echo "converting changelog.template" # define variables export VERSION=`cat VERSION` - export RELEASE=1 + export RELNUM=1 export DATE=`date -R` # envsubst FILES="${FILES} debian/changelog" for FILE in ${FILES}; do cat ${FILE}.template | envsubst > ${FILE} rm ${FILE}.template - cat ${FILE} + echo -e "\n---------- generated: ${FILE}" + # cat ${FILE} + # echo -e "---------- end of generated: ${FILE} \n" done } + case ${RELEASE} in + buster) make buster-debsource ;; + bionic) make bionic-debsource ;; + focal) make focal-debsource ;; + buster) make buster-debsource ;; + esac ;; *) # We expect only RPM by default [ -d rpm ] || { echo "using freshly cloned and adapted rpm folder" mv delme/rpm . + mv delme/debian . } # define variables export VERSION=`cat VERSION` - export RELEASE=1 - export DATE=`date +"%a %B %d %Y"` + export RELNUM=1 + export DATE=`date +"%a %b %d %Y"` # envsubst - FILES="${FILES} rpm/oidc-agent.spec" + FILES="debian/changelog ${FILES} rpm/oidc-agent.spec" for FILE in ${FILES}; do cat ${FILE}.template | envsubst > ${FILE} rm ${FILE}.template + echo -e "\n---------- generated: ${FILE}" cat ${FILE} + echo -e "---------- end of generated: ${FILE} \n" done ;; esac diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index f83521f7..0950cf51 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -34,55 +34,13 @@ variables: MTEAM_CI_ADDITIONAL_PACKAGES_ZYPPER: '' -build-debian-buster: - extends: - - .build-debian-buster - script: - - make buster-debsource - - dpkg-buildpackage -uc -us - allow_failure: true - -build-debian-bullseye: - extends: - - .build-debian-bullseye - -build-debian-bookworm: - extends: - - .build-debian-bookworm - -build-ubuntu-focal: - extends: - - .build-ubuntu-focal - script: - - make focal-debsource - - dpkg-buildpackage -uc -us - allow_failure: true - -build-ubuntu-bionic: - extends: - - .build-ubuntu-bionic - script: - - make bionic-debsource - - dpkg-buildpackage -uc -us - allow_failure: true - -build-ubuntu-jammy: - extends: - - .build-ubuntu-jammy - -build-ubuntu-kinetic: - extends: - - .build-ubuntu-kinetic - - - ###### WINDOWS ###### build-windows-lib-64: extends: - .build-win-msys2-mingw64 - .on-push-and-master variables: - DOCKER_IMAGE_VERSION_WINDOWS: '3' + DOCKER_IMAGE_VERSION_WINDOWS: 'latest' artifacts: paths: - bin @@ -106,7 +64,7 @@ build-windows-64: - .build-win-msys2 - .on-push-and-master variables: - DOCKER_IMAGE_VERSION_WINDOWS: '3' + DOCKER_IMAGE_VERSION_WINDOWS: 'latest' artifacts: paths: - bin @@ -182,7 +140,7 @@ installer: TRIGGER_BRANCH: main - if: $CI_COMMIT_REF_NAME != $CI_DEFAULT_BRANCH variables: - TRIGGER_BRANCH: devel + TRIGGER_BRANCH: devel # TODO change back to devel if 5 is merged inherit: variables: - ANYBRANCH_RESOLVE_DEPENDENCIES_REPO @@ -208,3 +166,4 @@ installer: project: m-team/oidc/oidc-agent-win-installer branch: $TRIGGER_BRANCH strategy: depend + diff --git a/CHANGELOG.md b/CHANGELOG.md index 3cc32280..10acc3c1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,100 @@ +## oidc-agent 5.0.0 + +oidc-agent 5 is a major update that brings the power of a true configuration file and focuses on improving the user +experience and usability. +**See our [migration guide](https://indigo-dc.gitbook.io/oidc-agent/migrating-to-oidc-agent-5) for details on how to +migrate to oidc-agent 5.** + +### Changes + +- Reworked the `issuer.config` file: + - The `issuer.config` file in `/etc/oidc-agent` is updated on package upgrade + - The `issuer.config` in user's oidc-agent dir is automatically updated when needed + - The new format allows to set and tweak options / behavior on a per-issuer basis, e.g. if the encryption password + should be stored. +- Dropped oidc-agent `--pw-lifetime` option. This did not work as expected. The intended usage can be achieved with + the `issuer.config` file. +- Dropped support for storing encryption password in system's keyring (`--pw-keyring`) + - This still can be done through `--pw-cmd` +- Changed the oidc-agent-service socket dir from `/tmp/oidc-agent-service/` to `/tmp/oidc-agent-service-`. + This allows (better) multiple users to run oidc-agent-service. + - This is a breaking change for all existing terminals that already have a `$OIDC_SOCK` set to a service socket. The + easiest way to make sure that also existing sessions with the old path have access to a newly started agent, + create a link from the old location to the new one, i.e. + ```bash + rm -rf /tmp/oidc-agent-service/${UID}/ + ln -s /tmp/oidc-agent-service-${UID} /tmp/oidc-agent-service/${UID} + ``` +- Also changed how the socket is managed by `oidc-agent-service`: Instead of linking the random socket location to a + well known location, we now create the socket directly in the well known location. This improves security + and `oidc-agent-service` can make use of the trust-checks on the socket location performed by the agent. + +### Features + +- Added support for RFC8707 to request ATs with specific audiences + - Changed default audience request method to RFC8707 + - Old audience request behavior can be enabled for issuers through the `issuer.config` file. + - For known IAM instances legacy aud mode is enabled by default +- Added support for `oidc-agent [command_args]`, similar to ssh-agent; e.g. `oidc-agent bash` starts the agent + and makes it available in a new bash. +- Added possibility for stat logging and sharing + - Sharing usage statistics helps us better understanding how users use oidc-agent and therefore helps us to improve + oidc-agent + +### Security Fixes: + +- Fixed permissions of agent socket. +- `oidc-agent` now checks the socket location to be trustworthy. + +### API + +- Added possibility to obtain (extended) account information from the agent. This includes all available accounts, + associated to their OP issuer, an indicator if the account is loaded or not, and an indicator if there is a public + client available for an issuer. +- Dropped deprecated functions from liboidc-agent +- Renamed numbered functions in liboidc-agent + +### Enhancements + +- A lot of the configuration options in the configuration file greatly improve the user experience, the following are + just a few examples of what is possible: + - Automatically store the encryption password for certain issuers + - Automatically encrypt new account configuration with gpg + - Automatically use a pre-registered client + - Automatically prefer configurations via a mytoken server if issuer is available there +- Improved text and styling of prompts. +- Several improvements to the windows installer +- Improvements to the gui prompting design +- Several smaller improvements + +### Bugfixes + +- Fixed a bug that potentially could cause a segmentation fault +- Fixed a bug related to http retrying that potentially could cause a segmentation fault +- Fixed a problem in oidc-agent-service where only one user could run oidc-agent-service +- Fixed a bug where wrong unlock attempts of agent locking did not increase/create delay +- Fixed more bugs + +### Dependencies + +- Dropped libsecret dependency + +### OpenID Provider + +- Added OP: https://alice-auth.web.cern.ch/ +- Added OP: https://atlas-auth.web.cern.ch/ +- Added OP: https://cms-auth.web.cern.ch/ +- Added OP: https://lhcb-auth.web.cern.ch/ +- Added OP: https://bildungsproxy.aai.dfn.de +- Added public client for https://bildungsproxy.aai.dfn.de +- Added OP: https://auth.didmos.nfdi-aai.de +- Added public client for https://auth.didmos.nfdi-aai.de +- Added OP: https://regapp.nfdi-aai.de/oidc/realms/nfdi_demo +- Added public client for https://regapp.nfdi-aai.de/oidc/realms/nfdi_demo + ## oidc-agent 4.5.2 ### Changes diff --git a/Makefile b/Makefile index d7940cd2..9deed9e4 100644 --- a/Makefile +++ b/Makefile @@ -35,8 +35,6 @@ PROMPT = oidc-prompt VERSION ?= $(shell cat VERSION) TILDE_VERSION := $(shell echo $(VERSION) | sed s/-pr/~pr/) -BASE_VERSION := $(shell head debian/changelog -n 1 | cut -d \( -f 2 | cut -d \) -f 1 | cut -d \- -f 1) -DEBIAN_VERSION := $(shell head debian/changelog -n 1 | cut -d \( -f 2 | cut -d \) -f 1 | sed s/-[0-9][0-9]*//) # DIST = $(lsb_release -cs) LIBMAJORVERSION ?= $(shell echo $(VERSION) | cut -d '.' -f 1) # Generated lib version / name @@ -49,7 +47,7 @@ SHARED_LIB_NAME_SHORT = liboidc-agent.dylib else ifdef ANY_MSYS SONAME = liboidc-agent.$(LIBMAJORVERSION).dll -SHARED_LIB_NAME_FULL = liboidc-agent.$(LIBVERSION).dll +SHARED_LIB_NAME_FULL = liboidc-agent.$(LIBMAJORVERSION).dll SHARED_LIB_NAME_SO = $(SONAME) SHARED_LIB_NAME_SHORT = liboidc-agent.dll IMPORT_LIB_NAME = liboidc-agent.lib @@ -75,7 +73,9 @@ APILIB = $(LIBDIR)/api MANDIR = man CONFDIR = config PROVIDERCONFIG = issuer.config -PUBCLIENTSCONFIG = pubclients.config +PROVIDERCONFIGD = issuer.config.d +PUBCLIENTSCONFIG = pubclients.config # only needed for uninstalling it from older (manual) installations +GLOBALCONFIG = config SERVICECONFIG = oidc-agent-service.options TESTSRCDIR = test/src @@ -94,6 +94,7 @@ MAN_PATH ?=$(PREFIX)/share/man PROMPT_MAN_PATH ?=$(PREFIX)/share/man CONFIG_PATH ?=$(PREFIX)/etc CONFIG_AFTER_INST_PATH ?=$(CONFIG_PATH) +TMPFILES_PATH ?=$(PREFIX)/usr/lib/tmpfiles.d else PREFIX ?= ifdef MINGW32 @@ -124,6 +125,7 @@ CONFIG_AFTER_INST_PATH ?=$(CONFIG_PATH) BASH_COMPLETION_PATH ?=$(PREFIX)/usr/share/bash-completion/completions DESKTOP_APPLICATION_PATH ?=$(PREFIX)/usr/share/applications XSESSION_PATH ?=$(PREFIX)/etc/X11 +TMPFILES_PATH ?=$(PREFIX)/usr/lib/tmpfiles.d endif endif endif @@ -162,7 +164,6 @@ LSODIUM = $(shell $(USE_PKG_CONFIG_PATH) pkg-config --libs libsodium) LARGP = -largp LMICROHTTPD = $(shell $(USE_PKG_CONFIG_PATH) pkg-config --libs libmicrohttpd) LCURL = -lcurl -LSECRET = -lsecret-1 LGLIB = -lglib-2.0 LLIST = -lclibs_list LCJSON = -lcjson @@ -184,7 +185,8 @@ CPPFLAGS += -std=c++11 CPPFLAGS += -fPIC ifndef MAC_OS ifndef ANY_MSYS -CPPFLAGS += $(shell pkg-config --cflags --libs gtk+-3.0 webkit2gtk-4.0) -lstdc++ +WEBKITGTK ?= webkit2gtk-4.0 +CPPFLAGS += $(shell pkg-config --cflags --libs gtk+-3.0 $(WEBKITGTK)) -lstdc++ endif endif ifndef MAC_OS @@ -194,8 +196,11 @@ ifndef NODPKG CFLAGS +=$(shell dpkg-buildflags --get CFLAGS) CPPFLAGS +=$(shell dpkg-buildflags --get CFLAGS) endif -CFLAGS += $(shell $(USE_PKG_CONFIG_PATH) pkg-config --cflags libsecret-1) endif +CFLAGS +=$(shell $(USE_PKG_CONFIG_PATH) pkg-config --cflags libsodium) +CFLAGS +=$(shell $(USE_PKG_CONFIG_PATH) pkg-config --cflags libmicrohttpd) +CFLAGS +=$(shell $(USE_PKG_CONFIG_PATH) pkg-config --cflags libqrencode) + TEST_CFLAGS = $(CFLAGS) -I. # Linker options @@ -231,11 +236,12 @@ ifeq ($(USE_LIST_SO),1) PROMPT_LFLAGS += $(LLIST) endif ifeq ($(USE_MUSTACHE_SO),1) + LFLAGS += $(LMUSTACHE) PROMPT_LFLAGS += $(LMUSTACHE) endif AGENT_LFLAGS = $(LCURL) $(LMICROHTTPD) $(LQR) $(LFLAGS) ifndef MAC_OS - AGENT_LFLAGS += $(LSECRET) $(LGLIB) + AGENT_LFLAGS += $(LGLIB) endif GEN_LFLAGS = $(LFLAGS) $(LMICROHTTPD) $(LQR) ADD_LFLAGS = $(LFLAGS) @@ -291,10 +297,16 @@ endif ifneq ($(USE_LIST_SO),1) LIB_SOURCES += $(LIBDIR)/list/list.c $(LIBDIR)/list/list_iterator.c $(LIBDIR)/list/list_node.c endif +APILIB_SOURCES := $(LIB_SOURCES) +ifneq ($(USE_MUSTACHE_SO),1) + LIB_SOURCES += $(sort $(shell find $(LIBDIR)/mustache -name "*.c")) +endif SOURCES := $(SRC_SOURCES) $(LIB_SOURCES) GENERAL_SOURCES := $(sort $(shell find $(SRCDIR)/utils -name "*.c") $(shell find $(SRCDIR)/account -name "*.c") $(shell find $(SRCDIR)/ipc -name "*.c") $(shell find $(SRCDIR)/defines -name "*.c") $(shell find $(SRCDIR)/api -name "*.c")) -ifndef ANY_MSYS +ifdef ANY_MSYS +GENERAL_SOURCES := $(sort $(filter-out $(shell find $(SRCDIR)/utils/file_io/safefile -name "*.c"), $(GENERAL_SOURCES))) +else GENERAL_SOURCES := $(sort $(filter-out $(SRCDIR)/utils/registryConnector.c, $(GENERAL_SOURCES))) endif AGENT_SOURCES_TMP := $(sort $(shell find $(SRCDIR)/$(AGENT) -name "*.c")) @@ -328,11 +340,12 @@ endif endif MUSTACHE_FILES := $(sort $(shell find $(PROMPT_SRCDIR)/html -name '*.mustache')) +PROMPTTEMPLATE_FILES := $(sort $(shell find $(SRCDIR)/utils/prompting/templates -name '*.mustache')) # Define objects ALL_OBJECTS := $(SRC_SOURCES:$(SRCDIR)/%.c=$(OBJDIR)/%.o) $(LIB_SOURCES:$(LIBDIR)/%.c=$(OBJDIR)/%.o) ALL_OBJECTS := $(ALL_OBJECTS:$(SRCDIR)/%.cc=$(OBJDIR)/%.o) -AGENT_OBJECTS := $(AGENT_SOURCES:$(SRCDIR)/%.c=$(OBJDIR)/%.o) $(GENERAL_SOURCES:$(SRCDIR)/%.c=$(OBJDIR)/%.o) $(LIB_SOURCES:$(LIBDIR)/%.c=$(OBJDIR)/%.o) $(OBJDIR)/$(GEN)/qr.o +AGENT_OBJECTS := $(AGENT_SOURCES:$(SRCDIR)/%.c=$(OBJDIR)/%.o) $(GENERAL_SOURCES:$(SRCDIR)/%.c=$(OBJDIR)/%.o) $(LIB_SOURCES:$(LIBDIR)/%.c=$(OBJDIR)/%.o) $(OBJDIR)/$(GEN)/qr.o $(OBJDIR)/$(GEN)/promptAndSet/name.o GEN_OBJECTS := $(GEN_SOURCES:$(SRCDIR)/%.c=$(OBJDIR)/%.o) $(GENERAL_SOURCES:$(SRCDIR)/%.c=$(OBJDIR)/%.o) $(OBJDIR)/oidc-agent/httpserver/termHttpserver.o $(OBJDIR)/oidc-agent/httpserver/running_server.o $(OBJDIR)/oidc-agent/oidc/device_code.o $(LIB_SOURCES:$(LIBDIR)/%.c=$(OBJDIR)/%.o) ADD_OBJECTS := $(ADD_SOURCES:$(SRCDIR)/%.c=$(OBJDIR)/%.o) $(GENERAL_SOURCES:$(SRCDIR)/%.c=$(OBJDIR)/%.o) $(LIB_SOURCES:$(LIBDIR)/%.c=$(OBJDIR)/%.o) PROMPT_OBJECTS := $(PROMPT_SOURCES:$(SRCDIR)/%.c=$(OBJDIR)/%.o) $(LIB_SOURCES:$(LIBDIR)/%.c=$(OBJDIR)/%.o) @@ -354,14 +367,16 @@ ifdef ANY_MSYS API_ADDITIONAL_OBJECTS += $(OBJDIR)/utils/registryConnector.o API_ADDITIONAL_OBJECTS += $(OBJDIR)/utils/file_io/oidc_file_io.o $(OBJDIR)/utils/file_io/file_io.o $(OBJDIR)/utils/file_io/fileUtils.o endif -API_OBJECTS := $(API_SOURCES:$(SRCDIR)/%.c=$(OBJDIR)/%.o) $(API_ADDITIONAL_OBJECTS) $(LIB_SOURCES:$(LIBDIR)/%.c=$(OBJDIR)/%.o) +API_OBJECTS := $(API_SOURCES:$(SRCDIR)/%.c=$(OBJDIR)/%.o) $(API_ADDITIONAL_OBJECTS) $(APILIB_SOURCES:$(LIBDIR)/%.c=$(OBJDIR)/%.o) ifdef MSYS PROMPT_OBJECTS += $(OBJDIR)/utils/file_io/oidc_file_io.o $(OBJDIR)/utils/file_io/fileUtils.o endif PIC_OBJECTS := $(API_OBJECTS:$(OBJDIR)/%=$(PICOBJDIR)/%) -CLIENT_OBJECTS := $(CLIENT_SOURCES:$(SRCDIR)/%.c=$(OBJDIR)/%.o) $(API_ADDITIONAL_OBJECTS) $(OBJDIR)/utils/disableTracing.o $(LIB_SOURCES:$(LIBDIR)/%.c=$(OBJDIR)/%.o) +CLIENT_OBJECTS := $(CLIENT_SOURCES:$(SRCDIR)/%.c=$(OBJDIR)/%.o) $(API_ADDITIONAL_OBJECTS) $(OBJDIR)/utils/config/client_config.o $(OBJDIR)/utils/config/configUtils.o $(OBJDIR)/utils/disableTracing.o $(LIB_SOURCES:$(LIBDIR)/%.c=$(OBJDIR)/%.o) +ifdef ANY_MSYS + CLIENT_OBJECTS += $(OBJDIR)/defines/settings.o +else ifndef MAC_OS -ifndef ANY_MSYS CLIENT_OBJECTS += $(OBJDIR)/utils/file_io/oidc_file_io.o $(OBJDIR)/utils/file_io/file_io.o $(OBJDIR)/utils/file_io/fileUtils.o endif endif @@ -506,6 +521,10 @@ $(PROMPT_SRCDIR)/html/templates.h: $(PROMPT_SRCDIR)/html/static/css/custom.css $ @cd $(PROMPT_SRCDIR)/html && ./gen.sh @echo "Generated "$@"" +$(SRCDIR)/utils/prompting/templates/templates.h: $(PROMPTTEMPLATE_FILES) + @cd $(SRCDIR)/utils/prompting/templates && ./gen.sh + @echo "Generated "$@"" + # Phony Installer .PHONY: install @@ -527,7 +546,7 @@ install_bin: $(BIN_PATH)/bin/$(AGENT) $(BIN_PATH)/bin/$(GEN) $(BIN_PATH)/bin/$(A @echo "Installed binaries" .PHONY: install_conf -install_conf: $(CONFIG_PATH)/oidc-agent/$(PROVIDERCONFIG) $(CONFIG_PATH)/oidc-agent/$(PUBCLIENTSCONFIG) $(CONFIG_PATH)/oidc-agent/$(SERVICECONFIG) +install_conf: $(CONFIG_PATH)/oidc-agent/$(PROVIDERCONFIGD) $(CONFIG_PATH)/oidc-agent/$(PROVIDERCONFIG) $(CONFIG_PATH)/oidc-agent/$(GLOBALCONFIG) $(CONFIG_PATH)/oidc-agent/$(SERVICECONFIG) $(TMPFILES_PATH)/oidc-agent.conf @echo "Installed config files" .PHONY: install_bash @@ -642,7 +661,11 @@ $(PROMPT_BIN_PATH)/bin/$(PROMPT): $(BINDIR)/$(PROMPT) $(PROMPT_BIN_PATH)/bin $(CONFIG_PATH)/oidc-agent/$(PROVIDERCONFIG): $(CONFDIR)/$(PROVIDERCONFIG) $(CONFIG_PATH)/oidc-agent @install -p -m 644 $< $@ -$(CONFIG_PATH)/oidc-agent/$(PUBCLIENTSCONFIG): $(CONFDIR)/$(PUBCLIENTSCONFIG) $(CONFIG_PATH)/oidc-agent +$(CONFIG_PATH)/oidc-agent/$(PROVIDERCONFIGD): $(CONFDIR)/$(PROVIDERCONFIGD) $(CONFIG_PATH)/oidc-agent + @install -d -p -m 755 $< $@ + @(cd $(CONFDIR) && find $(PROVIDERCONFIGD) -type f -exec install -m 644 "{}" "$(CONFIG_PATH)/oidc-agent/{}" \;) + +$(CONFIG_PATH)/oidc-agent/$(GLOBALCONFIG): $(CONFDIR)/$(GLOBALCONFIG) $(CONFIG_PATH)/oidc-agent @install -p -m 644 $< $@ $(CONFIG_PATH)/oidc-agent/$(SERVICECONFIG): $(CONFDIR)/$(SERVICECONFIG) $(CONFIG_PATH)/oidc-agent @@ -683,6 +706,15 @@ $(MAN_PATH)/man1/$(KEYCHAIN).1: $(MANDIR)/$(KEYCHAIN).1 $(MAN_PATH)/man1 $(PROMPT_MAN_PATH)/man1/$(PROMPT).1: $(MANDIR)/$(PROMPT).1 $(PROMPT_MAN_PATH)/man1 @install -p -m 644 $< $@ +## Tmpfiles +$(TMPFILES_PATH)/oidc-agent.conf: $(CONFDIR)/tmpfiles.d/oidc-agent.conf + @install -d -m 755 `dirname $@` + @install -p -m 644 $< $@ + +.PHONY: install_tmpfile +install_tmpfile: + + endif ifndef MSYS @@ -698,13 +730,13 @@ $(LIBDEV_PATH)/$(SHARED_LIB_NAME_SHORT): $(LIBDEV_PATH) @ln -sf $(SHARED_LIB_NAME_SO) $@ $(INCLUDE_PATH)/oidc-agent/%.h: $(SRCDIR)/api/%.h $(INCLUDE_PATH)/oidc-agent - @install -p $< $@ + @install -p -m 644 $< $@ $(INCLUDE_PATH)/oidc-agent/ipc_values.h: $(SRCDIR)/defines/ipc_values.h $(INCLUDE_PATH)/oidc-agent - @install -p $< $@ + @install -p -m 644 $< $@ $(INCLUDE_PATH)/oidc-agent/oidc_error.h: $(SRCDIR)/utils/oidc_error.h $(INCLUDE_PATH)/oidc-agent - @install -p $< $@ + @install -p -m 644 $< $@ $(LIBDEV_PATH)/liboidc-agent.a: $(APILIB)/liboidc-agent.a $(LIBDEV_PATH) @install -p $< $@ @@ -715,8 +747,8 @@ ifndef ANY_MSYS ## scheme handler $(DESKTOP_APPLICATION_PATH)/oidc-gen.desktop: $(CONFDIR)/scheme_handler/oidc-gen.desktop - @install -p -D $< $@ - @echo "Exec=x-terminal-emulator -e bash -c \"$(BIN_AFTER_INST_PATH)/bin/$(GEN) --codeExchange=%u; exec bash\"" >> $@ + @install -p -m 644 -D $< $@ + @echo "Exec=sh -c \"$(BIN_AFTER_INST_PATH)/bin/$(GEN) --codeExchange=%u; \\\\\$$SHELL\"" >> $@ ## Xsession $(XSESSION_PATH)/Xsession.d/91oidc-agent: $(CONFDIR)/Xsession/91oidc-agent @@ -765,6 +797,7 @@ uninstall_man: uninstall_conf: @$(rm) $(CONFIG_PATH)/oidc-agent/$(PROVIDERCONFIG) @$(rm) $(CONFIG_PATH)/oidc-agent/$(PUBCLIENTSCONFIG) + @$(rm) $(CONFIG_PATH)/oidc-agent/$(GLOBALCONFIG) @$(rm) $(CONFIG_PATH)/oidc-agent/$(SERVICECONFIG) @echo "Uninstalled config" diff --git a/VERSION b/VERSION index 6cedcff6..0062ac97 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -4.5.2 +5.0.0 diff --git a/config/config b/config/config new file mode 100644 index 00000000..10076f84 --- /dev/null +++ b/config/config @@ -0,0 +1,61 @@ +# You can use '#' comments in this file, but only for whole line comments (not end-of-line), i.e. +# ^ # This whole line is a comment and works fine +# ^ {"key": "here is some json at the begging"} # and the comment is only at the end, but this is not supported +# +# Low-traffic mailing list with updates such as critical security incidents and new releases: https://www.lists.kit.edu/sympa/subscribe/oidc-agent-user + +{ + "oidc-agent": { + # If oidc-agent does not find the correct cert bundle file automatically, you can set the default here + "cert-path": null, + "bind_address": null, + "confirm": false, + "autoload": true, + "auto-reauthenticate": true, + "auto-gen": true, + # Possible values are "max", "exact" + "auto-gen-scope-mode": "max", + "custom-uri-scheme": true, + "webserver": true, + "lifetime": 0, + "group": null, + "debug_logging": false, + # oidc-agent can collect information about the requests it receives; if you share this data with us, we can better + # understand how oidc-agent is used by our users and improve it further; all information collected in completely + # anonymized; you can see what information is collected yourself by looking into the $OIDCDIR/oidc-agent.stats file + # If 'stats_collect_local' is true statistics are collected + "stats_collect_local": false, + # If 'stats_collect_share' and 'stats_collect_local' are true statistics are collected and send to stats server + "stats_collect_share": true, + # If 'stats_collect_location' is true and statistics are collected it includes your country + "stats_collect_location": true + }, + "oidc-gen": { + "cnid": null, + "auto-open-url": true, + # If set, gpg encryption is used for new account configurations with the specified gpg key + "default_gpg_key": null, + "prompt": "cli", + "pw-prompt": "cli", + # If set, all confirmation prompts are answered according to the specified policy; possible values are 'default' (uses the default option), 'no', and 'yes' + "answer-confirm-prompts": null, + "default_mytoken_server": "https://mytoken.data.kit.edu", + # The 'default_mytoken_profile' is only for the oidc-gen + "default_mytoken_profile": "agent", + "prefer_mytoken_over_oidc": false, + "debug_logging": false + }, + "oidc-add": { + "store-pw": false, + "prompt": "cli", + "pw-prompt": "cli", + "debug_logging": false + }, + "oidc-token": { + # Default value for the -t option + "default_min_lifetime": 0, + # Default profile if a mytoken is requested but no profile given + "default_mytoken_profile": "web-default", + "debug_logging": false + } +} \ No newline at end of file diff --git a/config/issuer.config b/config/issuer.config index eecd2894..e69de29b 100644 --- a/config/issuer.config +++ b/config/issuer.config @@ -1,17 +0,0 @@ -https://iam-test.indigo-datacloud.eu/ https://iam-test.indigo-datacloud.eu/manage/dev/dynreg -https://iam.deep-hybrid-datacloud.eu/ https://iam.deep-hybrid-datacloud.eu/manage/dev/dynreg deep-hdc-iam-support@lists.cnaf.infn.it -https://iam.extreme-datacloud.eu/ https://iam.extreme-hybrid-datacloud.eu/manage/dev/dynreg deep-xdc-iam-support@lists.cnaf.infn.it -https://iam-demo.cloud.cnaf.infn.it/ https://iam-demo.cloud.cnaf.infn.it/manage/dev/dynreg -https://b2access.eudat.eu:8443/oauth2 https://b2access.eudat.eu/ -https://b2access-integration.fz-juelich.de/oauth2 https://b2access-integration.fz-juelich.de/ -https://login-dev.helmholtz.de/oauth2 https://login-dev.helmholtz.de/ -https://login.helmholtz.de/oauth2 https://login.helmholtz.de/ -https://services.humanbrainproject.eu/oidc/ https://services.humanbrainproject.eu/oidc/manage/dev/dynreg platform@humanbrainproject.eu -https://accounts.google.com https://console.developers.google.com/ -https://aai-dev.egi.eu/auth/realms/egi https://aai.egi.eu/federation/egi/services egi-aai-checkin@lists.grnet.gr -https://aai-demo.egi.eu/auth/realms/egi https://aai.egi.eu/federation/egi/services egi-aai-checkin@lists.grnet.gr -https://aai.egi.eu/auth/realms/egi egi-aai-checkin@lists.grnet.gr -https://login.elixir-czech.org/oidc/ https://login.elixir-czech.org/oidc/manage/dev/dynreg/new aai-contact@elixir-europe.org -https://oidc.scc.kit.edu/auth/realms/kit https://www.scc.kit.edu/dienste/openid-connect.php https://www.scc.kit.edu/personen/matthias.bonn.php -https://wlcg.cloud.cnaf.infn.it/ https://wlcg.cloud.cnaf.infn.it/manage/dev/dynreg - diff --git a/config/issuer.config.d/bildungsproxy b/config/issuer.config.d/bildungsproxy new file mode 100644 index 00000000..d823ec19 --- /dev/null +++ b/config/issuer.config.d/bildungsproxy @@ -0,0 +1,9 @@ +{ + "issuer": "https://bildungsproxy.aai.dfn.de", + "contact": "hotline@aai.dfn.de", + "pubclient": { + "client_id": "kit-oidc-agent-sso-client", + "client_secret": "fa8e1df34f1ddb80d34cf6edbe67701b5f65f697e5b083604e5d67f29834b01a" + }, + "pw-store": true +} diff --git a/config/issuer.config.d/deep-datacloud b/config/issuer.config.d/deep-datacloud new file mode 100644 index 00000000..80998930 --- /dev/null +++ b/config/issuer.config.d/deep-datacloud @@ -0,0 +1,10 @@ +{ + "issuer": "https://iam.deep-hybrid-datacloud.eu/", + "register": "https://iam.deep-hybrid-datacloud.eu/manage/dev/dynreg", + "contact": "deep-hdc-iam-support@lists.cnaf.infn.it", + "pubclient": { + "client_id": "f1f5d80d-849e-4324-9676-8b103604281f", + "scope": "address iam phone openid profile offline_access eduperson_scoped_affiliation email eduperson_entitlement" + }, + "legacy_aud_mode": true +} diff --git a/config/issuer.config.d/egi b/config/issuer.config.d/egi new file mode 100644 index 00000000..69d9db62 --- /dev/null +++ b/config/issuer.config.d/egi @@ -0,0 +1,8 @@ +{ + "issuer": "https://aai.egi.eu/auth/realms/egi", + "contact": "egi-aai-checkin@lists.grnet.gr", + "pubclient": { + "client_id": "oidc-agent", + "scope": "openid profile email offline_access eduperson_entitlement eduperson_scoped_affiliation eduperson_unique_id" + } +} diff --git a/config/issuer.config.d/egi-demo b/config/issuer.config.d/egi-demo new file mode 100644 index 00000000..9ac02086 --- /dev/null +++ b/config/issuer.config.d/egi-demo @@ -0,0 +1,9 @@ +{ + "issuer": "https://aai-demo.egi.eu/auth/realms/egi", + "register": "https://aai.egi.eu/federation/egi/services", + "contact": "egi-aai-checkin@lists.grnet.gr", + "pubclient": { + "client_id": "oidc-agent", + "scope": "openid profile email offline_access eduperson_entitlement eduperson_scoped_affiliation eduperson_unique_id" + } +} diff --git a/config/issuer.config.d/egi-dev b/config/issuer.config.d/egi-dev new file mode 100644 index 00000000..46f82cff --- /dev/null +++ b/config/issuer.config.d/egi-dev @@ -0,0 +1,9 @@ +{ + "issuer": "https://aai-dev.egi.eu/auth/realms/egi", + "register": "https://aai.egi.eu/federation/egi/services", + "contact": "egi-aai-checkin@lists.grnet.gr", + "pubclient": { + "client_id": "oidc-agent", + "scope": "openid profile email offline_access eduperson_entitlement eduperson_scoped_affiliation eduperson_unique_id" + } +} diff --git a/config/issuer.config.d/elixir b/config/issuer.config.d/elixir new file mode 100644 index 00000000..7e42d267 --- /dev/null +++ b/config/issuer.config.d/elixir @@ -0,0 +1,9 @@ +{ + "issuer": "https://login.elixir-czech.org/oidc/", + "register": "https://login.elixir-czech.org/oidc/manage/dev/dynreg/new", + "contact": "aai-contact@elixir-europe.org", + "pubclient": { + "client_id": "ef13f849-b1ce-4c10-b28f-4a5f19b84fe0", + "scope": "country address forwardedScopedAffiliations phone openid offline_access profile bona_fide_status email eduperson_entitlement" + } +} diff --git a/config/issuer.config.d/eudat b/config/issuer.config.d/eudat new file mode 100644 index 00000000..7fab06f5 --- /dev/null +++ b/config/issuer.config.d/eudat @@ -0,0 +1,8 @@ +{ + "issuer": "https://b2access.eudat.eu:8443/oauth2", + "register": "https://b2access.eudat.eu/", + "pubclient": { + "client_id": "public-oidc-agent", + "client_secret": "rE9CsA4T4UkgSVccErSD" + } +} diff --git a/config/issuer.config.d/extreme-datacloud b/config/issuer.config.d/extreme-datacloud new file mode 100644 index 00000000..00d0f5c9 --- /dev/null +++ b/config/issuer.config.d/extreme-datacloud @@ -0,0 +1,10 @@ +{ + "issuer": "https://iam.extreme-datacloud.eu/", + "register": "https://iam.extreme-hybrid-datacloud.eu/manage/dev/dynreg", + "contact": "deep-xdc-iam-support@lists.cnaf.infn.it", + "pubclient": { + "client_id": "441d0d59-bfb3-430e-8142-10b73cae3cc4", + "scope": "address phone openid profile offline_access eduperson_scoped_affiliation email eduperson_entitlement wlcg" + }, + "legacy_aud_mode": true +} diff --git a/config/issuer.config.d/google b/config/issuer.config.d/google new file mode 100644 index 00000000..30b2aa2c --- /dev/null +++ b/config/issuer.config.d/google @@ -0,0 +1,12 @@ +{ + "issuer": "https://accounts.google.com", + "register": "https://console.developers.google.com/", + "pubclient": { + "client_id": "854595242827-tkecrgj4b8bpchermrba6vs5ig7a4o2c.apps.googleusercontent.com", + "client_secret": "o7nZynvNCrp7u4WbhqopIpd1", + "scope": "openid profile", + "flow": [ + "device" + ] + } +} diff --git a/config/issuer.config.d/helmholtz b/config/issuer.config.d/helmholtz new file mode 100644 index 00000000..c4e6e856 --- /dev/null +++ b/config/issuer.config.d/helmholtz @@ -0,0 +1,8 @@ +{ + "issuer": "https://login.helmholtz.de/oauth2", + "register": "https://login.helmholtz.de/", + "pubclient": { + "client_id": "public-oidc-agent", + "client_secret": "rE9CsA4T4UkgSVccErSD" + } +} diff --git a/config/issuer.config.d/helmholtz-dev b/config/issuer.config.d/helmholtz-dev new file mode 100644 index 00000000..3db8fd8d --- /dev/null +++ b/config/issuer.config.d/helmholtz-dev @@ -0,0 +1,8 @@ +{ + "issuer": "https://login-dev.helmholtz.de/oauth2", + "register": "https://login-dev.helmholtz.de/", + "pubclient": { + "client_id": "public-oidc-agent", + "client_secret": "rE9CsA4T4UkgSVccErSD" + } +} diff --git a/config/issuer.config.d/iam-demo b/config/issuer.config.d/iam-demo new file mode 100644 index 00000000..bf2ed2d9 --- /dev/null +++ b/config/issuer.config.d/iam-demo @@ -0,0 +1,9 @@ +{ + "issuer": "https://iam-demo.cloud.cnaf.infn.it/", + "register": "https://iam-demo.cloud.cnaf.infn.it/manage/dev/dynreg", + "pubclient": { + "client_id": "222df837-7c79-431f-ae80-5a75ec56e324", + "scope": "address phone openid email profile offline_access" + }, + "legacy_aud_mode": true +} diff --git a/config/issuer.config.d/indigo-datacloud b/config/issuer.config.d/indigo-datacloud new file mode 100644 index 00000000..3f67b101 --- /dev/null +++ b/config/issuer.config.d/indigo-datacloud @@ -0,0 +1,9 @@ +{ + "issuer": "https://iam-test.indigo-datacloud.eu/", + "register": "https://iam-test.indigo-datacloud.eu/manage/dev/dynreg", + "pubclient": { + "client_id": "70806df4-3fad-4c6c-a7e8-49b554c85b6f", + "scope": "address phone openid profile offline_access eduperson_scoped_affiliation aarc email eduperson_entitlement" + }, + "legacy_aud_mode": true +} diff --git a/config/issuer.config.d/kit b/config/issuer.config.d/kit new file mode 100644 index 00000000..b1e1c3fc --- /dev/null +++ b/config/issuer.config.d/kit @@ -0,0 +1,10 @@ +{ + "issuer": "https://oidc.scc.kit.edu/auth/realms/kit", + "register": "https://www.scc.kit.edu/dienste/openid-connect.php", + "contact": "https://www.scc.kit.edu/personen/matthias.bonn.php", + "pubclient": { + "client_id": "7b3b85df-1965-41b9-b4e2-476f0eb0d5df", + "scope": "openid profile email address phone base memberOf orcid microprofile-jwt entitlements offline_access" + }, + "pw-store": true +} diff --git a/config/issuer.config.d/nfdi-didmos b/config/issuer.config.d/nfdi-didmos new file mode 100644 index 00000000..eb17c73a --- /dev/null +++ b/config/issuer.config.d/nfdi-didmos @@ -0,0 +1,9 @@ +{ + "issuer": "https://auth.didmos.nfdi-aai.de", + "contact": "info@daasi.de", + "pubclient": { + "client_id": "jeMzkTsgCH5kleUr", + "client_secret": "yjODEFwFYZCC4eEy7x4bs7oX", + "scope": "openid email eduperson_assurance eduperson_scoped_affiliation entitlements offline_access orcid profile schac_home_organization voperson_external_affiliation voperson_external_id" + } +} diff --git a/config/issuer.config.d/nfdi-regapp b/config/issuer.config.d/nfdi-regapp new file mode 100644 index 00000000..bf904c83 --- /dev/null +++ b/config/issuer.config.d/nfdi-regapp @@ -0,0 +1,10 @@ +{ + "issuer": "https://regapp.nfdi-aai.de/oidc/realms/nfdi_demo", + "contact": "fels-support@lists.kit.edu", + "pubclient": { + "client_id": "public_demo_nfdi", + "client_secret": "public", + "scope": "openid profile email" + }, + "pw-store": true +} diff --git a/config/issuer.config.d/wlcg b/config/issuer.config.d/wlcg new file mode 100644 index 00000000..301c9ea5 --- /dev/null +++ b/config/issuer.config.d/wlcg @@ -0,0 +1,25 @@ +[{ + "issuer": "https://wlcg.cloud.cnaf.infn.it/", + "register": "https://wlcg.cloud.cnaf.infn.it/manage/dev/dynreg", + "pubclient": { + "client_id": "7d6ad1c2-6647-4846-b0d5-c9f6ad0987d0", + "scope": "openid profile email address phone offline_access wlcg wlcg.groups storage.read:/ storage.create:/ storage.modify:/ compute.read compute.modify compute.create compute.cancel eduperson_entitlement eduperson_scoped_affiliation" + }, + "legacy_aud_mode": true +}, { + "issuer": "https://alice-auth.web.cern.ch/", + "register": "https://alice-auth.web.cern.ch/manage/dev/dynreg", + "legacy_aud_mode": true +}, { + "issuer": "https://atlas-auth.web.cern.ch/", + "register": "https://atlas-auth.web.cern.ch/manage/dev/dynreg", + "legacy_aud_mode": true +}, { + "issuer": "https://cms-auth.web.cern.ch/", + "register": "https://cms-auth.web.cern.ch/manage/dev/dynreg", + "legacy_aud_mode": true +}, { + "issuer": "https://lhcb-auth.web.cern.ch/", + "register": "https://lhcb-auth.web.cern.ch/manage/dev/dynreg", + "legacy_aud_mode": true +}] \ No newline at end of file diff --git a/config/pubclients.config b/config/pubclients.config deleted file mode 100644 index f0892e0d..00000000 --- a/config/pubclients.config +++ /dev/null @@ -1,26 +0,0 @@ -# This file contains public clients that are usable by oidc-gen. -# You don't have to edit this file. To pass client credentials (and other -# information) to oidc-gen you can also use the -f option. -# -# The file has the following format: Each line contains one entry (i.e. one -# public client). oidc-gen expects that there is only one entry per issuer. To -# be more precise the first match will be used. Each entry must have the -# following format: -# [:]@issuer[@] -# is a space separated list of scopes - -854595242827-tkecrgj4b8bpchermrba6vs5ig7a4o2c.apps.googleusercontent.com:o7nZynvNCrp7u4WbhqopIpd1@https://accounts.google.com/@openid profile -7b3b85df-1965-41b9-b4e2-476f0eb0d5df@https://oidc.scc.kit.edu/auth/realms/kit/ -70806df4-3fad-4c6c-a7e8-49b554c85b6f@https://iam-test.indigo-datacloud.eu/@address phone openid profile offline_access eduperson_scoped_affiliation aarc email eduperson_entitlement -f1f5d80d-849e-4324-9676-8b103604281f@https://iam.deep-hybrid-datacloud.eu/@address iam phone openid profile offline_access eduperson_scoped_affiliation email eduperson_entitlement -441d0d59-bfb3-430e-8142-10b73cae3cc4@https://iam.extreme-datacloud.eu/@address phone openid profile offline_access eduperson_scoped_affiliation email eduperson_entitlement wlcg -222df837-7c79-431f-ae80-5a75ec56e324@https://iam-demo.cloud.cnaf.infn.it/@address phone openid email profile offline_access -oidc-agent@https://aai-dev.egi.eu/auth/realms/egi@openid profile email offline_access eduperson_entitlement eduperson_scoped_affiliation eduperson_unique_id -oidc-agent@https://aai-demo.egi.eu/auth/realms/egi@openid profile email offline_access eduperson_entitlement eduperson_scoped_affiliation eduperson_unique_id -oidc-agent@https://aai.egi.eu/auth/realms/egi@openid profile email offline_access eduperson_entitlement eduperson_scoped_affiliation eduperson_unique_id -7c98b37b-71ab-4b35-aade-25642d4695e6@https://services.humanbrainproject.eu/oidc/@hbp.users hbp.documents hbp.gerrit offline_access hbp.task hbp.provenance openid hbp.collab hbp.collab.self hbp.groups profile -ef13f849-b1ce-4c10-b28f-4a5f19b84fe0@https://login.elixir-czech.org/oidc/@country address forwardedScopedAffiliations phone openid offline_access profile bona_fide_status email eduperson_entitlement -7d6ad1c2-6647-4846-b0d5-c9f6ad0987d0@https://wlcg.cloud.cnaf.infn.it/@openid profile email address phone offline_access wlcg wlcg.groups storage.read:/ storage.create:/ storage.modify:/ compute.read compute.modify compute.create compute.cancel eduperson_entitlement eduperson_scoped_affiliation -public-oidc-agent:rE9CsA4T4UkgSVccErSD@https://login.helmholtz.de/oauth2 -public-oidc-agent:rE9CsA4T4UkgSVccErSD@https://login-dev.helmholtz.de/oauth2 -public-oidc-agent:rE9CsA4T4UkgSVccErSD@https://b2access.eudat.eu/oauth2/ diff --git a/config/tmpfiles.d/oidc-agent.conf b/config/tmpfiles.d/oidc-agent.conf new file mode 100644 index 00000000..afa7d39f --- /dev/null +++ b/config/tmpfiles.d/oidc-agent.conf @@ -0,0 +1,3 @@ +# /tmp/oidc-agent-service/* is used for symlinks to the actual agent instance +# which may run for many days. This following line prevents systemd from removing this content. +x /tmp/oidc-agent-service/* diff --git a/gitbook/SUMMARY.md b/gitbook/SUMMARY.md index 1850d6cd..cca0ae9b 100644 --- a/gitbook/SUMMARY.md +++ b/gitbook/SUMMARY.md @@ -2,16 +2,17 @@ * [Introduction](README.md) * [Quickstart](quickstart.md) -* [Installation]() +* [Migrating to oidc-agent 5](oidc-agent5.md) +* [Installation](installation/intro.md) * [Linux](installation/install.md) * [MacOS](installation/macos.md) * [Windows](installation/windows.md) * [Configuration](configure.md) * [oidc-agent Directory](configuration/directory.md) - * [Set Default Account for a Provider](configuration/default-accounts.md) + * [General Configuration](configuration/config.md) + * [Configuration of Providers](configuration/issuers.md) * [oidc-agent Integration](configuration/integration.md) * [Agent Forwarding](configuration/forwarding.md) - * [Other Configuration](configuration/other.md) * [Usage](user.md) * [oidc-agent](oidc-agent/oidc-agent.md) * [Starting oidc-agent](oidc-agent/start.md) @@ -51,7 +52,7 @@ * [MAC OS](macos/macos.md) * [State of Feature Support](macos/state.md) * [Installation](macos/installation.md) -* [Security]() +* [Security](security/intro.md) * [Privilege Separation & Architecture](security/privilege-separation.md) * [Account Configuration Files](security/account-configs.md) * [Credentials](security/credentials.md) diff --git a/gitbook/api/api-c.md b/gitbook/api/api-c.md index e3fc7c51..d5416c0a 100644 --- a/gitbook/api/api-c.md +++ b/gitbook/api/api-c.md @@ -1,7 +1,7 @@ -## liboidc-agent4 +## liboidc-agent5 The C-API provides functions for getting an access token for a specific configuration as well as the associated issuer. -These functions are designed for easy usage. The C-API is available as a shared library through the `liboidc-agent4` +These functions are designed for easy usage. The C-API is available as a shared library through the `liboidc-agent5` package. The developement files (i.e. header-files) and the static library are included in the `liboidc-agent-dev` package. @@ -16,14 +16,14 @@ The following functions can be used to obtain an access token for a specific acc you / your application does not know which account configuration should be used, but you know for which provider you need an access token you can also [request an access token for a provider](#requesting-an-access-token-for-a-provider). -#### getAccessToken3 +#### getAccessToken It is recommended to use [`getAgentTokenResponse`](#getagenttokenresponse) instead. ```c -char* getAccessToken3(const char* accountname, time_t min_valid_period, - const char* scope, const char* application_hint, - const char* audience) +char* getAccessToken(const char* accountname, time_t min_valid_period, + const char* scope, const char* application_hint, + const char* audience) ``` This function requests an access token from oidc-agent for the `accountname` @@ -54,7 +54,7 @@ On failure `NULL` is returned and `oidc_errno` is set A complete example can look the following: ```c -char* token = getAccessToken3(accountname, 60, NULL, +char* token = getAccessToken(accountname, 60, NULL, "example-app", NULL); if(token == NULL) { oidcagent_perror(); @@ -65,16 +65,6 @@ if(token == NULL) { } ``` -#### getAccessToken2 - -This function is deprecated and should not be used in new applications. Use -[`getAccessToken3`](#getaccesstoken3) or [`getAgentTokenResponse`](#getagenttokenresponse) instead. - -#### getAccessToken - -This function is deprecated and should not be used in new applications. Use -[`getAccessToken3`](#getaccesstoken3) or [`getAgentTokenResponse`](#getagenttokenresponse) instead. - #### getAgentTokenResponse ```c @@ -144,29 +134,19 @@ if(response.type == AGENT_RESPONSE_TYPE_ERROR) { secFreeAgentResponse(response); ``` -#### getTokenResponse3 - -This function is deprecated and should not be used in new applications. Use -[`getAgentTokenResponse`](#getagenttokenresponse) instead. - -#### getTokenResponse - -This function is deprecated and should not be used in new applications. Use -[`getAgentTokenResponse`](#getagenttokenresponse) instead. - ### Requesting an Access Token For a Provider -The `getAccessTokenForIssuer3` and `getAgentTokenResponseForIssuer` methods can be used to obtain an access token for a +The `getAccessTokenForIssuer` and `getAgentTokenResponseForIssuer` methods can be used to obtain an access token for a specific OpenID Provider (issuer). This is useful for applications that only work with a specific provider and therefore know the issuer for which they need an access token, but do not require the user to provide an account configuration shortname. -#### getAccessTokenForIssuer3 +#### getAccessTokenForIssuer ```c -char* getAccessTokenForIssuer3(const char* issuer_url, time_t min_valid_period, - const char* scope, const char* application_hint, - const char* audience) +char* getAccessTokenForIssuer(const char* issuer_url, time_t min_valid_period, + const char* scope, const char* application_hint, + const char* audience) ``` This function requests an access token from oidc-agent for the provider with @@ -187,7 +167,7 @@ the `audience` audience. ##### Return Value The function returns only the access token as a `char*`. To additionally obtain other information -use [`getTokenResponseForIssuer3`](#gettokenresponseforissuer3). After usage the return value MUST be freed +use [`getAgentTokenResponseForIssuer`](#getagenttokenresponseforissuer). After usage the return value MUST be freed using `secFree`. On failure `NULL` is returned and `oidc_errno` is set @@ -198,7 +178,7 @@ On failure `NULL` is returned and `oidc_errno` is set A complete example can look the following: ```c -char* token = getAccessTokenForIssuer3("https://example.com/", 60, NULL, +char* token = getAccessTokenForIssuer("https://example.com/", 60, NULL, "example-app", NULL); if(token == NULL) { oidcagent_perror(); @@ -209,13 +189,6 @@ if(token == NULL) { } ``` -#### getAccessTokenForIssuer - -This function is deprecated and should not be used in new applications. Use -[`getAccessTokenForIssuer3`](#getaccesstokenforissuer3) -or [`getAgentTokenResponseForIssuer`](#getagenttokenresponseforissuer) -instead. - #### getAgentTokenResponseForIssuer ```c @@ -285,16 +258,6 @@ if(response.type == AGENT_RESPONSE_TYPE_ERROR) { secFreeAgentResponse(response); ``` -#### getTokenResponseForIssuer - -This function is deprecated and should not be used in new applications. Use -[`getAgentTokenResponseForIssuer`](#getagenttokenresponseforissuer) instead. - -#### getTokenResponseForIssuer3 - -This function is deprecated and should not be used in new applications. Use -[`getAgentTokenResponseForIssuer`](#getagenttokenresponseforissuer) instead. - ### Requesting a Mytoken #### getAgentMytokenResponse @@ -311,7 +274,8 @@ mytoken account configuration. The mytoken should have the properties defined by ##### Parameters - `accountname` is the shortname of the account configuration that should be used. -- `mytoken_profile` is a [mytoken profile](https://mytoken-docs.data.kit.edu/concepts/profiles) describing the properties of the requested mytoken. +- `mytoken_profile` is a [mytoken profile](https://mytoken-docs.data.kit.edu/concepts/profiles) describing the + properties of the requested mytoken. - `application_hint` should be the name of the application that requests the mytoken. This string might be displayed to the user for authorization purposes. @@ -370,12 +334,14 @@ char* getMytoken(const char* accountname, const char* mytoken_profile, const cha ``` This function requests mytoken from oidc-agent for the `accountname` -mytoken account configuration. The mytoken should have the properties defined by the passed [`mytoken_profile`](https://mytoken-docs.data.kit.edu/concepts/profiles/). +mytoken account configuration. The mytoken should have the properties defined by the +passed [`mytoken_profile`](https://mytoken-docs.data.kit.edu/concepts/profiles/). ##### Parameters - `accountname` is the shortname of the account configuration that should be used. -- `mytoken_profile` is a [mytoken profile](https://mytoken-docs.data.kit.edu/concepts/profiles) describing the properties of the requested mytoken. +- `mytoken_profile` is a [mytoken profile](https://mytoken-docs.data.kit.edu/concepts/profiles) describing the + properties of the requested mytoken. - `application_hint` should be the name of the application that requests the mytoken. This string might be displayed to the user for authorization purposes. @@ -547,14 +513,14 @@ freed. This function behaves similar to `strerror(errno)`. #### Error Codes -| error code | explanation | -|------------|-------------| -| OIDC_SUCCESS | success - no error | -| OIDC_EERROR | general error - check the error string| -| OIDC_ENOACCOUNT | the account is not loaded| -| OIDC_EOIDC | an error related to OpenID Connect happened - check the error string| -| OIDC_EENVVAR | the environment variable used to locate the agent is not set| -| OIDC_ECONSOCK | could not connect to the oidc-agent socket - most likely the agent is not running| -| OIDC_ELOCKED| the agent is locked and first has to be unlocked by the user| -| OIDC_EFORBIDDEN|the user forbid this action| -| OIDC_EPASS | wrong password - might occur if the account was not loaded and the user entered a wrong password in the autoload prompt| +| error code | explanation | +|-----------------|-------------------------------------------------------------------------------------------------------------------------| +| OIDC_SUCCESS | success - no error | +| OIDC_EERROR | general error - check the error string | +| OIDC_ENOACCOUNT | the account is not loaded | +| OIDC_EOIDC | an error related to OpenID Connect happened - check the error string | +| OIDC_EENVVAR | the environment variable used to locate the agent is not set | +| OIDC_ECONSOCK | could not connect to the oidc-agent socket - most likely the agent is not running | +| OIDC_ELOCKED | the agent is locked and first has to be unlocked by the user | +| OIDC_EFORBIDDEN | the user forbid this action | +| OIDC_EPASS | wrong password - might occur if the account was not loaded and the user entered a wrong password in the autoload prompt | diff --git a/gitbook/api/api-ipc.md b/gitbook/api/api-ipc.md index fc69c5d3..dab59a45 100644 --- a/gitbook/api/api-ipc.md +++ b/gitbook/api/api-ipc.md @@ -13,15 +13,15 @@ The following fields and values have to be present for the different calls: #### Request -| field | value | Requirement Level | -|------------------|----------------------------------------|-------------------| -| request | access_token | REQUIRED | -| account | <account_shortname> | REQUIRED if 'issuer' not used | +| field | value | Requirement Level | +|------------------|----------------------------------------|--------------------------------| +| request | access_token | REQUIRED | +| account | <account_shortname> | REQUIRED if 'issuer' not used | | issuer | <issuer_url> | REQUIRED if 'account' not used | -| min_valid_period | <min_valid_period> [s] | RECOMMENDED | -| application_hint | <application_name> | RECOMMENDED | -| scope | <space delimited list of scopes> | OPTIONAL | -| audience | <audience for the token> | OPTIONAL | +| min_valid_period | <min_valid_period> [s] | RECOMMENDED | +| application_hint | <application_name> | RECOMMENDED | +| scope | <space delimited list of scopes> | OPTIONAL | +| audience | <audience for the token> | OPTIONAL | Note that one of the fields `account` and `issuer` has to be present. Use `account` to request an access token for a specific account configuration and `issuer` when you do not know which account configuration should be used but you do @@ -57,11 +57,11 @@ used account configuration. #### Response -| field | value | -|--------------|----------------| -| status | success | -| access_token | <access_token> | -| issuer | <issuer_url> | +| field | value | +|--------------|-------------------------| +| status | success | +| access_token | <access_token> | +| issuer | <issuer_url> | | expires_at | <expiration time> | Example: @@ -77,11 +77,11 @@ Example: #### Error Response -| field | value | -|--------|---------------------| -| status | failure | +| field | value | +|--------|---------------------------| +| status | failure | | error | <error_description> | -| info | <help_message> | +| info | <help_message> | The help message in the `info` key is optionally and therefore might be omitted. @@ -98,12 +98,12 @@ Example: #### Request -| field | value | Requirement Level | -|------------------|----------------------------------------|-------------------| -| request | mytoken | REQUIRED | -| account | <account_shortname> | REQUIRED | -| mytoken_profile | <mytoken profile> | RECOMMENDED | -| application_hint | <application_name> | RECOMMENDED | +| field | value | Requirement Level | +|------------------|---------------------------|-------------------| +| request | mytoken | REQUIRED | +| account | <account_shortname> | REQUIRED | +| mytoken_profile | <mytoken profile> | RECOMMENDED | +| application_hint | <application_name> | RECOMMENDED | ##### Example @@ -133,13 +133,13 @@ expires after seven days. #### Response -| field | value | -|----------------|----------------| -| status | success | -| mytoken | <mytoken or transfer_code> | +| field | value | +|----------------|--------------------------------------------| +| status | success | +| mytoken | <mytoken or transfer_code> | | mytoken_issuer | <issuer_url of the mytoken instance> | -| oidc_issuer | <issuer_url of the OP> | -| expires_at | <expiration time> | +| oidc_issuer | <issuer_url of the OP> | +| expires_at | <expiration time> | Additionally, fields included in the [mytoken's server response](https://mytoken-docs.data.kit.edu/dev/api/latest/endpoints/mytoken/#mytoken-response), @@ -163,11 +163,11 @@ Example: #### Error Response -| field | value | -|--------|---------------------| -| status | failure | +| field | value | +|--------|---------------------------| +| status | failure | | error | <error_description> | -| info | <help_message> | +| info | <help_message> | The help message in the `info` key is optionally and therefore might be omitted. @@ -184,9 +184,9 @@ Example: #### Request -| field | value | Requirement Level | -|------------------|----------------------------------------|-------------------| -| request | loaded_accounts | REQUIRED | +| field | value | Requirement Level | +|---------|-----------------|-------------------| +| request | loaded_accounts | REQUIRED | ##### Examples @@ -198,10 +198,10 @@ Example: #### Response -| field | value | -|--------------|----------------| -| status | success | -| info | <list of loaded accounts> | +| field | value | +|--------|---------------------------------| +| status | success | +| info | <list of loaded accounts> | Example: @@ -218,9 +218,9 @@ Example: #### Error Response -| field | value | -|--------|---------------------| -| status | failure | +| field | value | +|--------|---------------------------| +| status | failure | | error | <error_description> | Example: diff --git a/gitbook/configuration/config.md b/gitbook/configuration/config.md new file mode 100644 index 00000000..0eba2246 --- /dev/null +++ b/gitbook/configuration/config.md @@ -0,0 +1,11 @@ +# General Configuration + +Starting with oidc-agent `5.0.0` oidc-agent now has a central configuration file. Before, (apart from some exceptions) +it was only possible to use command line options to tweak behavior. +With the new configuration file a lot of the options can be set globally. + +The `config` file can be located in `/etc/oidc-agent` or the oidc-agent directory. If both files are present the +configurations are merged, where options from the user's oidc-agent directory overwrite options specified in the global +config file. +The file is structured into sections for the different tools and configuration options should be self-explaining or +explained in the commented default configuration file. \ No newline at end of file diff --git a/gitbook/configuration/directory.md b/gitbook/configuration/directory.md index dccdbd94..a88ad708 100644 --- a/gitbook/configuration/directory.md +++ b/gitbook/configuration/directory.md @@ -1,15 +1,17 @@ ## oidc-agent Directory -An oidc-agent directory will be created when using oidc-gen for the first time. + +An oidc-agent directory will be created when using oidc-gen for the first time. Depending on the existence of `~/.config` it will be located at one of these locations: + - `~/.config/oidc-agent` - `~/.oidc-agent` -Alternatively the location can also be set through an environment variable +Alternatively, the location can also be set through an environment variable called `OIDC_CONFIG_DIR`. However, we note that this environment variable has to be present whenever you use one of the `oidc-` binaries. It is therefore -recommend to set it in the `.bash_profile` or similar. +recommend to set it in the `.bash_profile` or similar. All account configuration files generated by `oidc-gen` are saved in this -oidc-agent directory. Additionally there is a config file named `issuer.config`. This file can be used to specify a list of issuers (one issuer per line) that are used as suggestions by `oidc-gen`. `oidc-gen` will also update this issuer list after an account configuration was created successfully. oidc-agent installs a similar file under `/etc/oidc-agent`, however, that file should not be edited by the user, but it might be updated with new oidc-agent versions. +oidc-agent directory. Additionally, there are some config files located in this directory. diff --git a/gitbook/configuration/issuers.md b/gitbook/configuration/issuers.md new file mode 100644 index 00000000..2dc356d6 --- /dev/null +++ b/gitbook/configuration/issuers.md @@ -0,0 +1,51 @@ +# Configuration of Providers + +Prior to oidc-agent version `5.0.0` the `issuer.config` file was used to have a list of issuers (OpenID Providers) +that `oidc-gen` used as suggestions. +It could also be used to set a default account for each issuer. A separate file `pubclients.config` was used to +configure public clients. + +In oidc-agent 5 and beyond these files have been merged into a single, more powerful configuration file about issuers. +The `issuer.config` file can contain a json array of json objects each describing an issuer. It is also possible to +split configuration of issuers into separate files. +The `issuer.config.d` directory can contain config files that each hold the json object configuration for one issuer. + +oidc-agent combines the issuer configuration from these locations (the lowest entry has the highest priority): + +- `/etc/oidc-agent/issuer.config.d/*` +- `/etc/oidc-agent/issuer.config` +- `$OIDC_CONFIG_DIR/issuer.config.d/*` +- `$OIDC_CONFIG_DIR/issuer.config` + +An issuer config object can have the following fields: + +| Field Name | Description | +|---------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `issuer` | The issuer url | +| `manual_register` | A url where a client can be registered manually | +| `contact` | Contact information for this issuer | +| `configuration_endpoint` | The url of the configuration endpoint if it cannot be derived from the issuer url | +| `device_authorization_endpoint` | The url of the device authorization endpoint if it is not published at the configuration endpoint | +| `cert_path` | The local certificate bundle path that should be used when communicating with the issuer | +| `store_pw` | Indicates if the encryption password should be kept in memory, so that the account configuration file can be updated without prompting the user for the password again | +| `oauth` | Indicates that this is an oauth2 instead of an OIDC issuer | +| `legacy_aud_mode` | Indicates that this OIDC issuer does not support RFC 8707 for requesting ATs with a specific audience and the mechanism from oidc-agent<5 should be used (space-delimited list in the 'audience' parameter). | +| `pubclient` | Information about a public client for this issuer | + +Additionally, the following properties are supported, but should only be given in the `issuer.config` file located in +the oidc-agent directory. + +- `default_account`: The name of the default account config; if not given the first account config in the `accounts` + field is used as a default. +- `accounts`: A list of all the available accounts for this issuer; MUST not be edited manually, this field is managed + by + the agent. + +The `pubclient` field is an object that can have the following fields: + +| Field Name | Description | +|-----------------|-----------------------------------------------------------------------------------------------------------------------| +| `client_id` | The client id of the public client | +| `client_secret` | If given the client secret of the public client | +| `scope` | The scopes available for this public client | +| `flows` | The oidc flows supported by this public client; possible values are the same as for the `--flow` option of `oidc-gen` | diff --git a/gitbook/configuration/other.md b/gitbook/configuration/other.md deleted file mode 100644 index a46968f6..00000000 --- a/gitbook/configuration/other.md +++ /dev/null @@ -1,8 +0,0 @@ -## Other Configuration - -Generally oidc-agent does not use any configuration files. -(For the cases where configuration files are used they are placed in `/etc/oidc-agent/` or in -the [agent directory](../directory.md)) -Configuration needed is done mostly through command line options to the different components and in some cases through -environment variables. If some command line options are used for every call, it makes sense to define an alias for it -in `.bashrc` or `.bash_aliases`, e.g. `alias oidc-add="oidc-add --pw-store=3600"`. diff --git a/gitbook/installation/intro.md b/gitbook/installation/intro.md new file mode 100644 index 00000000..d340213a --- /dev/null +++ b/gitbook/installation/intro.md @@ -0,0 +1,4 @@ +# Installation + +`oidc-agent` is avialable on [Linux](install.md), [MacOS](macos.md), and [Windows](windows.md). The installataion method +differs depending on your operating system. Please refer to the relevant page. diff --git a/gitbook/macos/state.md b/gitbook/macos/state.md index 777b954e..9f635881 100644 --- a/gitbook/macos/state.md +++ b/gitbook/macos/state.md @@ -11,6 +11,5 @@ - syslog -> we implemented a custom logging behavior. The log file can be found in the oidc-agent directory. - Obviously, now oidc-agent has to write to disk, which sort of breaks privilege separation. - bash completion -- storing a password in the keyring -> if one can make it work, pull requests / instructions are welcome. - Xsession integration - some enhancements might not work properly (e.g. http-server might not be killed in all cases when the agent dies) diff --git a/gitbook/oidc-add/options.md b/gitbook/oidc-add/options.md index fbd38fd3..5c79f3aa 100644 --- a/gitbook/oidc-add/options.md +++ b/gitbook/oidc-add/options.md @@ -10,7 +10,6 @@ * [`--pw-env`](#pw-env) * [`--pw-file`](#pw-file) * [`--pw-gpg`](#pw-gpg) -* [`--pw-keyring`](#pw-keyring) * [`--pw-prompt`](#pw-prompt) * [`--pw-store`](#pw-store) * [`--remove`](#remove) @@ -99,15 +98,6 @@ The `--pw-gpg`, `--pw-pgp`, `--gpg`, or `--pgp` option can be used to indicate t the `gpg-agent` should be used. However, with `oidc-add` this option is usually not needed, because we can detect pgp encryption from the account configuration file. -### `--pw-keyring` - -When this option is provided, the encryption password will be stored by -`oidc-agent` in the system's default keyring. See [`--pw-store`](#pw-store) for information on why `oidc-agent` might -need the encryption password. - -See [Encryption Passwords](../security/encryption-passwords.md) for security related information about the -different `--pw-*` options. - ### `--pw-prompt` This option can be used to change how `oidc-add` prompts the user for the encryption password. Possible values are `cli` @@ -120,8 +110,9 @@ When this option is provided, the encryption password will be kept in memory by `oidc-agent` (in an encrypted way). Usually none of the `--pw-*` options is needed, because `oidc-agent` does not have to read or update the account configuration file after loading. However, some OpenID Providers might use rotating refresh tokens. This means that for those providers `oidc-agent` has to update the client configuration file whenever a -new access token is retrieved from the OpenID Provider. If non of the -`--pw-*` options is provided, this means that the user will always be prompted to enter the encryption password. Because +new access token is retrieved from the OpenID Provider. If none of the +`--pw-*` options are provided, this means that the user will always be prompted to enter the encryption password. +Because this can get annoying, it is recommended to use any of the `--pw-*` options in such a case. For providers that are effected by this we included notes in the [Help for different providers](../provider/provider.md). diff --git a/gitbook/oidc-agent-service/oidc-agent-service.md b/gitbook/oidc-agent-service/oidc-agent-service.md index 270d8e9f..711c66c0 100644 --- a/gitbook/oidc-agent-service/oidc-agent-service.md +++ b/gitbook/oidc-agent-service/oidc-agent-service.md @@ -1,4 +1,5 @@ # oidc-agent-service + `oidc-agent-service` can be used to easily restart `oidc-agent`. `oidc-agent-service` is called in the same way as `oidc-agent`, this means that `oidc-agent-service` will print out the needed shell commands to set environment @@ -7,17 +8,22 @@ Therefore `oidc-agent-service` is usually called with `eval` or its output is piped to a file. ## Quick Start + Make `oidc-agent` available in the current terminal: + ``` eval `oidc-agent-service use` ``` + Restart the agent (it will still be usable in all terminals as before after the restart): + ``` oidc-agent-service restart-s ``` ## Configuration + The behavior of `oidc-agent-service` can be configured through a configuration file. Among others, this file can be used to set the command line options used when starting the agent. @@ -27,9 +33,14 @@ You can also add a `oidc-agent-service.options` file to your [oidc-agent directory](../configuration/directory.md). Options specified in this file will overwrite any option defined in `/etc/oidc-agent/oidc-agent-service.options`. +Please note that with oidc-agent 5 a proper configuration file was introduced; therefore, it is possible to configure +the started agent through that file. However, the above mentioned way is still supported. An overwrites options +specified in the configuration file. ## Commands + ### `use` + `use` will give you an usable agent. This is usually the command you want to use to start an agent. It starts an agent and makes it available (it prints the needed environment @@ -37,22 +48,27 @@ variables). If `oidc-agent-service` has already started an agent for you, this agent will we reused and made available. ### `start` + `start` starts an agent. If `oidc-agent-service` already started an agent, `start` will fail. If you want to reuse that agent in this case, use `use`. ### `restart` + `restart` restarts the agent. This means that the current agent is stopped and a new agent is started. On default the new agent is started with the same options as the old one. This behaviour can be changed (see [configuration](#configuration)). ### `restart-s` + `restart-s` is the same as `restart`, but does not print any output. Therefore, you can call `oidc-agent-service restart-s` instead of ``eval `oidc-agent-service restart-s` ``. ### `stop` + `stop` stops the running agent. ### `kill` + Same as `stop`. diff --git a/gitbook/oidc-agent/options.md b/gitbook/oidc-agent/options.md index 9cc80e5b..9a6c5d37 100644 --- a/gitbook/oidc-agent/options.md +++ b/gitbook/oidc-agent/options.md @@ -14,7 +14,6 @@ | [`--no-autoreauthenticate`](#no-autoreauthenticate) |Disables the automatic re-authentication feature| | [`--no-scheme`](#no-scheme) | `oidc-agent` will not use a custom uri scheme redirect [Only applies if authorization code flow is used]| | [`--no-webserver`](#no-webserver) | `oidc-agent` will not start a webserver [Only applies if authorization code flow is used]| -| [`--pw-store`](#pw-store) |Keeps the encryption passwords for all loaded account configurations encrypted in memory [..]| | [`--quiet`](#quiet) |Disable informational messages to stdout| | [`--lifetime`](#lifetime) |Sets a default value in seconds for the maximum lifetime of account configurations [..]| | [`--log-stderr`](#log-stderr) |Additionally prints log messages to stderr| @@ -128,16 +127,6 @@ be completed. Either by using a redirect uri that follows the custom redirect ur scheme `edu.kit.data.oidc-agent:/` - this will directly redirect to oidc-gen, or by copying the url the browser would normally redirect to and pass it to `oidc-gen --codeExchange`. -### `--pw-store` - -When this option is provided, the encryption password for all account configurations will be kept in memory by -`oidc-agent` (in an encrypted way). - -This option can also be sued with `oidc-add`. When this option is used with -`oidc-agent` it applies to all loaded account configuration; when used with -`oidc-add` only for that specific one. See [`oidc-add --pw-store`](../oidc-add/options.md#pw-store) for more -information. - ### `--quiet` Silences informational messages. Currently only has effect on the generated bash echo when setting agent environments. diff --git a/gitbook/oidc-agent5.md b/gitbook/oidc-agent5.md new file mode 100644 index 00000000..49ffe85f --- /dev/null +++ b/gitbook/oidc-agent5.md @@ -0,0 +1,99 @@ +## Migrating to oidc-agent 5 + +oidc-agent 5 is a major release with quite some usability improvements but unfortunately also some breaking changes. +However, most things should just work as you are used to. + +**All users of `oidc-agent-service` (this includes the default integration into the system on Linux) need to make a +little adjustment to be able to access the new agent from existing terminal sessions.** +The socket path location of `oidc-agent-service` was changed, therefore the old location has be linked to the new one. +**After** restarting the agent with `eval $(oidc-agent-service restart)`, run the following commands: + +```bash +rm -rf /tmp/oidc-agent-service/${UID}/ +ln -s /tmp/oidc-agent-service-${UID} /tmp/oidc-agent-service/${UID} +``` + +While we recommend to take a few minutes to configure version 5 of oidc-agent, so it suits your needs best, further +adaptions are not required. +After restarting the agent you could just use version 5 of oidc-agent as you are used to without any modifications. + +Nevertheless, it is a good idea to go through the following points: + +### Adapt Configuration + +Copy the new global configuration file `/etc/oidc-agent/config` to your user's oidc-agent +directory (`~/.config/oidc-agent` or `~/.oidc-agent`). +Go through the file and adapt it to your needs - but of course you can also keep the default configuration; +the default configuration represents what you are used to from oidc-agent 4 (you can also delete entries that you do not +change, but you don't have to). + +One option we would like to draw your attention to is the `default_gpg_key` option under `oidc-gen`. If you set a gpg +key id there, all newly created account configurations will be encrypted with that gpg key. No encryption passwords are +needed. + +#### Statistics + +Version 5 of oidc-agent can collect information about the requests it receives and build up a request log. +If you share this data with us, we can better understand how oidc-agent is used by our users and improve it further. +All information collected is completely anonymized and **no security sensitive data is collected**. +You can check yourself what information is collected by looking into the $OIDCDIR/oidc-agent.stats file. +In our opinion, the most sensitive piece of information is the `country` where you use oidc-agent. Collection of this +claim can be disabled/enabled separately. + +**By default, no information is collected.** +Requests logs are only collected by the agent if the `stats_collect_local` is set to `true` in the config file. +Furthermore, these are only shared with us if additionally the `stats_collect_share` option is set to `true`. +Collection of the request country can be controlled with the `stats_collect_location` option. + +Since it really helps us to get a better understanding on how oidc-agent is used in practice we kindly ask you to enable +the collection and sharing of these information. + +An example request entry looks the following: + +```json +{ + "machine_id": "0000000fbf182739b3849d8200000000_1000", + "boot_id": "00000000-b84b-4f7d-b8f2-600000000000_1000", + "os_info": "Linux 6.1.0-10-amd64 #1 SMP PREEMPT_DYNAMIC Debian 6.1.38-2 (2023-07-27) x86_64 GNU/Linux", + "time": 1693301191, + "lt_offset": 7200, + "agent_version": "5.0.0", + "country": "DE", + "request": "access_token", + "account": "kit", + "application_hint": "oidc-token", + "min_valid_period": 0 +} +``` + +### Restart the agent + +It's now time to restart your agent with the new version 5. + +```bash +eval `oidc-agent-service restart` +``` + +### Load your accounts + +While this is not required, we recommend you to load all your accounts once after migrating to version 5. This way the +agent can build the new `issuer.config` file in your oidc-agent directory and map all your account configs to the +correct issuer. This is useful when clients request access tokens for an issuer rather than for a specific account +config. + +### Update encryption + +If you configured a default gpg key in the config and want to migrate existing password encrypted account configs to +gpg-based encryption you can do so by running + +```bash +oidc-gen -u +``` + +for each of the relevant account configs. + +### Special Issuer Needs? + +If you want to configure different behavior for some issuers have a look at +the [`issuer.config` file](configuration/issuers.md). +If you make changes, please restart the agent again. \ No newline at end of file diff --git a/gitbook/security/account-configs.md b/gitbook/security/account-configs.md index c9a257a5..a2b3d30f 100644 --- a/gitbook/security/account-configs.md +++ b/gitbook/security/account-configs.md @@ -1,9 +1,15 @@ ## Account Configuration Files + The generated account configuration files contain sensitive information (i.e. client credentials and the refresh token) and are therefore stored in an encrypted way. -All encryption done in the `oidc-agent` project are done through the +There are two options to encrypt account configurations: + +- via `gpg-agent` +- password-based + +All encryption (except `gpg`-based) done in the `oidc-agent` project is done through the [`libsodium library`](https://github.com/jedisct1/libsodium), which is also used by software such as `Discord`, `RavenDB`, or `Wire`. diff --git a/gitbook/security/agent-locking.md b/gitbook/security/agent-locking.md index b0796e1b..20ad7314 100644 --- a/gitbook/security/agent-locking.md +++ b/gitbook/security/agent-locking.md @@ -1,13 +1,16 @@ ## Agent Locking + The agent can be locked using a locking password. While being locked the only -operation allowed are: +operations allowed are: + - checking if the agent is running - unlocking the agent -Every other request will result in an error `Agent locked`. This allows a -user to temporarily forbid all operations / requests without removing the loaded -account configurations. + Every other request will result in an error `Agent locked`. This allows a + user to temporarily forbid all operations / requests without removing the loaded + account configurations. -When the agent is locked refresh tokens, access tokens, and client credentials are encrypted using the locking password provided by the user. +While the agent is locked, refresh tokens, access tokens, and client credentials are encrypted using the locking +password provided by the user. The agent also offers brute force protection. When trying to unlock the agent with a wrong password a small delay is added, which will increase with the diff --git a/gitbook/security/autounload.md b/gitbook/security/autounload.md index 0943802c..9fb81731 100644 --- a/gitbook/security/autounload.md +++ b/gitbook/security/autounload.md @@ -1,7 +1,11 @@ ## Autounload (Lifetime) -Generally, we keep all information in memory as short as possible, but sometimes we have to keep information for a longer time, e.g. the account configuration. -As well as an encryption password that is kept encrypted in memory can automatically removed after a specified time, an account configuration can also be removed after some time. -A user can use the lifetime option to control how long a configuration will live in the agent, after that time it is automatically unloaded. -This feature plays very well with the autoload feature, because it makes it easy to use small lifetimes on default, because an unloaded configuration can be loaded again into the agent without running oidc-add, but simply when it is required, but it still requires user interaction. - +Generally, we keep all information in memory as short as possible, but sometimes we have to keep information for a +longer time, e.g. the account configuration. +Loaded account configurations can be automatically unloaded after a user-defined timespan. +A user can use the lifetime option to control how long a configuration will live in the agent, after that time it is +automatically unloaded. +This feature plays very well with the autoload feature, because it makes it easy to use small lifetimes on default, +because an unloaded configuration can be loaded again into the agent without running oidc-add, but simply when it is +required. +If this is combined with an `gpg`-based encryption the experience can be further improved. diff --git a/gitbook/security/communication.md b/gitbook/security/communication.md index 02863f20..8cb66c50 100644 --- a/gitbook/security/communication.md +++ b/gitbook/security/communication.md @@ -1,4 +1,5 @@ ## Communication + Because the oidc-agent project consists of multiple components and also other applications can interface with `oidc-agent`, communication between these components is an important part of the project. @@ -15,13 +16,16 @@ started `oidc-agent` to communicate with the agent. A man-in-the-middle attack on this socket would be possible, e.g. using `socat`. However, it requires an attacker to already have user privileges. -Also sensitive information is encrypted. +Also, sensitive information is encrypted. -`oidc-gen` and `oidc-add` encrypt all their communication with the agent (their communication might contain sensitive information like user credentials (only for `oidc-gen` when using the password flow), OIDC refresh token, client credentials, lock password, etc.) -Communication done with `liboidc-agent` (including `oidc-token`) is not encrypted. However, this communication only contains OIDC access token as sensitive information and these can be requested by any application with access to the agent socket. +`oidc-gen` and `oidc-add` encrypt all their communication with the agent (their communication might contain sensitive +information like user credentials (only for `oidc-gen` when using the password flow), OIDC refresh token, client +credentials, lock password, etc.) +Communication done with `liboidc-agent` (including `oidc-token`) is also encrypted. If an application communicates directly through the UNIX domain socket with `oidc-agent` encryption is theoretically supported. -However, it requires usage of libsodium and the implementation details (used functions, parameters, etc.) are not documented and have to be retrieved from the source. +However, it requires usage of libsodium and the implementation details (used functions, parameters, etc.) are not +documented and have to be retrieved from the source. Internally `oidc-agent` consists of two components that communicate through unnamed pipes. This communication is not encrypted, because it cannot be accessed by diff --git a/gitbook/security/credentials.md b/gitbook/security/credentials.md index dc6249ed..ed42d1de 100644 --- a/gitbook/security/credentials.md +++ b/gitbook/security/credentials.md @@ -1,19 +1,22 @@ ## Credentials + (User) credentials are very sensitive information and have to be handled with adequate caution. ### User Credentials + The user only has to pass its credentials (for the OpenID Provider) to `oidc-agent` when using the password flow. This flow has to be explicitly enabled to use it with `oidc-gen`. Furthermore, it is not even supported by most -providers and if it might require manual approval from an OpenID Provider -administrator. +providers and if, it might require manual approval from an OpenID Provider +administrator. It is recommended to use one of the other flows. However, when user credentials are passed to `oidc-agent` we handle them carefully and keep them as short as possible in memory. Credentials are also -overwritten before the memory is freed (see also [memory](memory.md)) and never -stored on disk. +overwritten before the memory is freed (see also [memory](memory.md)) and on disk only stored in the encrypted account +configuration. ### Refresh Tokens + OpenID Connect refresh tokens can be used to obtain additional access tokens and must be kept secret. The refresh token is stored encrypted in the account configuration file (s. [account configuration @@ -24,10 +27,10 @@ autoload](../tips.md#autoloading-and-autounloading-account-configurations)) also `oidc-agent` reads the refresh token from the encrypted account configuration file. However, `oidc-gen` and `oidc-add` do not use the refresh token, they only pass it to `oidc-agent`. `oidc-agent` uses the -refresh token to obtain additional access tokens. +refresh token to obtain additional access tokens. -**The agent has to keep the refresh token in memory. However, when not being -used it will be obfuscated, so it is harder to extract it from a memory dump. +**The agent has to keep the refresh token in memory. However, when it is not used +it will be obfuscated, so it is harder to extract it from a memory dump. The password used for this obfuscation is dynamically generated when the agent starts.** diff --git a/gitbook/security/encryption-passwords.md b/gitbook/security/encryption-passwords.md index 184fb37f..1b8d4e3e 100644 --- a/gitbook/security/encryption-passwords.md +++ b/gitbook/security/encryption-passwords.md @@ -1,6 +1,10 @@ ## Encryption Passwords + Generally, the encryption password provided by the user to encrypt / decrypt -account configurations is kept in memory as short as possible. However, `oidc-agent` can keep them longer in memory (encrypted) so it can update the configuration file when a provider issues a new refresh token. This option is not enabled on default and has to be enabled explicitly by the user. +account configurations is kept in memory as short as possible. However, `oidc-agent` can keep them longer in memory ( +encrypted) so it can update the configuration file when a provider issues a new refresh token. This option is not +enabled on default and has to be enabled explicitly by the user. +With oidc-agent 5 this option can be set on a per-issuer basis, and is enabled by default for certain issuers. If the user did not enable any password caching feature and `oidc-agent` needs an encryption password because it has to update the account @@ -9,39 +13,29 @@ if a provider uses rotating refresh tokens, this might be impractical, because the user has to enter his encryption password whenever a new access token is issued. We therefore implemented different password caching features: + - `oidc-agent` can keep the encryption password in memory. The encryption - password will be well encrypted; and the password used for this - encryption is dynamically generated when the agent starts. The time - how long the encryption password should be kept in memory can also be - limited, i.e. after that time the password will be cleared from - memory. To use this approach use the `--pw-store` option of - `oidc-add`. -- `oidc-agent` can also save the encryption password in the system's - password manager (keyring). Because any other application running as - the same user then has access to this password, `oidc-agent` still - applies encryption on the user's password, even though the keyring - will save the password encrypted. However, to prevent other - applications to obtain the plain password, we only store the encrypted - password. To use this approach use the `--pw-keyring` option of - `oidc-add`. + password will be well encrypted; and the password used for this + encryption is dynamically generated when the agent starts. To use this approach use the `--pw-store` option of + `oidc-add` or `oidc-agent` or (recommended) adapt the `issuer.config`. - `oidc-agent` can also retrieve the encryption password from a user - provided command; the output of this command will be used as the - encryption password. The command used will be kept encrypted in - memory, because it is used to obtain the encryption password without - any additional checks, it should be treated the same way as the - encryption password itself. Because with this option the user does not - have to enter his password at any point (also not when loading the - account configuration with `oidc-add`) it might be especially useful - when writing scripts. To use this apporach use the `--pw-cmd` option - of `oidc-add` or `oidc-gen`. + provided command; the output of this command will be used as the + encryption password. The command used will be kept encrypted in + memory, because it is used to obtain the encryption password (potentially) without + any additional checks, it should be treated the same way as the + encryption password itself. Because with this option the user (potentially) does not + have to enter his password at any point (also not when loading the + account configuration with `oidc-add`) it might be especially useful + when writing scripts. To use this approach use the `--pw-cmd` option + of `oidc-add` or `oidc-gen`. - `oidc-agent` can also retrieve the encryption password from a user - provided file; the content of this file will be used as the encryption - password. The filepath used will be kept encrypted in memory, because - it is used to obtain the encryption password without any additional - checks, it should be treated the same way as the encryption password - itself. Because with this option the user does not have to enter his - password at any point (also not when loading the account configuration - with `oidc-add`) it might be especially useful when writing scripts. - To use this apporach use the `--pw-file` option of `oidc-add` or - `oidc-gen`. + provided file; the content of this file will be used as the encryption + password. The filepath used will be kept encrypted in memory, because + it is used to obtain the encryption password without any additional + checks, it should be treated the same way as the encryption password + itself. Because with this option the user does not have to enter his + password at any point (also not when loading the account configuration + with `oidc-add`) it might be especially useful when writing scripts. + To use this approach use the `--pw-file` option of `oidc-add` or + `oidc-gen`. diff --git a/gitbook/security/intro.md b/gitbook/security/intro.md new file mode 100644 index 00000000..50ed3297 --- /dev/null +++ b/gitbook/security/intro.md @@ -0,0 +1,6 @@ +# Security + +We take security of `oidc-agent` serious and put our best effort into making `oidc-agent` as secure as possible while +preserving usability. + +In the following pages we describe different security aspects. diff --git a/gitbook/security/privilege-separation.md b/gitbook/security/privilege-separation.md index 4c622da0..71f1634f 100644 --- a/gitbook/security/privilege-separation.md +++ b/gitbook/security/privilege-separation.md @@ -19,44 +19,3 @@ The oidc-agent project consists of the following components: agent’s [API](../api/api.md). One example application for obtain access tokens is `oidc-token`. ![Architecture of the oidc-agent project](https://raw.githubusercontent.com/indigo-dc/oidc-agent/master/gitbook/images/architecture.png) - -### Privileges Needed by Different Components - -The following list might not be complete when it comes to implementation details -(e.g. privileges needed for obtaining the current time). - -Privileges for the different components: - -- oidc-agent-daemon: - - pipe ipc - - socket ipc - - internet - - also reads the CA bundle file - - starts web server -- oidc-agent-proxy: - - creates directory in `/tmp` - - creates socket in the created temporary directory - - pipe ipc - - socket ipc - - reads files in the oidc-agent directory - - might read other files if a path says so - - writes files in the oidc-agent directory - - calls binaries (i.e. oidc-prompt) - - might execute the user provided `--pw-cmd` -- oidc-gen: - - reads files in the oidc-agent directory - - writes files in the oidc-agent directory - - writes files in `/tmp` - - writes files passed to `--output` - - reads files passed to `--file`, `--print` - - socket ipc - - might call `xdg-open` to open the authorization url in a browser - - might execute the user provided `--pw-cmd` -- oidc-add: - - reads files in the oidc-agent directory - - reads random - - socket ipc - - might execute the user provided `--pw-cmd` -- oidc-token: - - socket ipc - diff --git a/src/oidc-prompt/mustache/LICENSE.txt b/lib/mustache/LICENSE.txt similarity index 100% rename from src/oidc-prompt/mustache/LICENSE.txt rename to lib/mustache/LICENSE.txt diff --git a/src/oidc-prompt/mustache/mustach-cjson.c b/lib/mustache/mustach-cjson.c similarity index 100% rename from src/oidc-prompt/mustache/mustach-cjson.c rename to lib/mustache/mustach-cjson.c diff --git a/src/oidc-prompt/mustache/mustach-cjson.h b/lib/mustache/mustach-cjson.h similarity index 100% rename from src/oidc-prompt/mustache/mustach-cjson.h rename to lib/mustache/mustach-cjson.h diff --git a/src/oidc-prompt/mustache/mustach-wrap.c b/lib/mustache/mustach-wrap.c similarity index 100% rename from src/oidc-prompt/mustache/mustach-wrap.c rename to lib/mustache/mustach-wrap.c diff --git a/src/oidc-prompt/mustache/mustach-wrap.h b/lib/mustache/mustach-wrap.h similarity index 100% rename from src/oidc-prompt/mustache/mustach-wrap.h rename to lib/mustache/mustach-wrap.h diff --git a/src/oidc-prompt/mustache/mustach.c b/lib/mustache/mustach.c similarity index 100% rename from src/oidc-prompt/mustache/mustach.c rename to lib/mustache/mustach.c diff --git a/src/oidc-prompt/mustache/mustach.h b/lib/mustache/mustach.h similarity index 100% rename from src/oidc-prompt/mustache/mustach.h rename to lib/mustache/mustach.h diff --git a/src/oidc-prompt/mustache-wrapper.h b/lib/wrapper/mustache.h similarity index 100% rename from src/oidc-prompt/mustache-wrapper.h rename to lib/wrapper/mustache.h diff --git a/src/account/account.c b/src/account/account.c index 53476980..e26c78cf 100644 --- a/src/account/account.c +++ b/src/account/account.c @@ -4,14 +4,14 @@ #include "defines/oidc_values.h" #include "defines/settings.h" #include "issuer_helper.h" -#include "utils/file_io/fileUtils.h" +#include "utils/config/agent_config.h" +#include "utils/config/issuerConfig.h" #include "utils/file_io/file_io.h" #include "utils/file_io/oidc_file_io.h" #include "utils/json.h" #include "utils/listUtils.h" #include "utils/logger.h" #include "utils/matcher.h" -#include "utils/pubClientInfos.h" #include "utils/string/stringUtils.h" #include "utils/uriUtils.h" @@ -47,7 +47,39 @@ int account_matchByIssuerUrl(const struct oidc_account* p1, } /** - * reads the pubclient.conf file and updates the account struct if a public + * reads the issuers config files and updates the account struct if a user + * defined client is found for that issuer, also setting the redirect uris + * @param account the account struct to be updated + * @return the updated account struct, or @c NULL + */ +struct oidc_account* updateAccountWithUserClientInfo( + struct oidc_account* account) { + if (account == NULL) { + oidc_setArgNullFuncError(__func__); + return NULL; + } + const struct issuerConfig* c = getIssuerConfig(account_getIssuerUrl(account)); + if (c == NULL || c->pub_client == NULL) { + return account; + } + const struct clientConfig* user_client = c->user_client; + if (user_client == NULL) { + return account; + } + account_setClientId(account, oidc_strcopy(user_client->client_id)); + account_setClientSecret(account, oidc_strcopy(user_client->client_secret)); + logger(DEBUG, "Using user defined client with id '%s' and secret '%s'", + user_client->client_id, user_client->client_secret); + if (user_client->redirect_uris) { + account_setRedirectUris(account, copyList(user_client->redirect_uris)); + } else { + account_setRedirectUris(account, defaultRedirectURIs()); + } + return account; +} + +/** + * reads the issuers config files and updates the account struct if a public * client is found for that issuer, also setting the redirect uris * @param account the account struct to be updated * @return the updated account struct, or @c NULL @@ -58,25 +90,34 @@ struct oidc_account* updateAccountWithPublicClientInfo( oidc_setArgNullFuncError(__func__); return NULL; } - struct pubClientInfos* pub = getPubClientInfos(account_getIssuerUrl(account)); - if (pub == NULL) { + const struct issuerConfig* c = getIssuerConfig(account_getIssuerUrl(account)); + if (c == NULL || c->pub_client == NULL) { return account; } + const struct clientConfig* pub = c->pub_client; account_setClientId(account, oidc_strcopy(pub->client_id)); account_setClientSecret(account, oidc_strcopy(pub->client_secret)); logger(DEBUG, "Using public client with id '%s' and secret '%s'", pub->client_id, pub->client_secret); - secFreePubClientInfos(pub); account_setRedirectUris(account, defaultRedirectURIs()); account_setUsesPubClient(account); return account; } +char* getScopesForUserClient(const struct oidc_account* p) { + const struct issuerConfig* c = getIssuerConfig(account_getIssuerUrl(p)); + if (c == NULL || c->user_client == NULL) { + return NULL; + } + return oidc_strcopy(c->user_client->scope); +} + char* getScopesForPublicClient(const struct oidc_account* p) { - struct pubClientInfos* pub = getPubClientInfos(account_getIssuerUrl(p)); - char* scope = pub ? oidc_strcopy(pub->scope) : NULL; - secFreePubClientInfos(pub); - return scope; + const struct issuerConfig* c = getIssuerConfig(account_getIssuerUrl(p)); + if (c == NULL || c->pub_client == NULL) { + return NULL; + } + return oidc_strcopy(c->pub_client->scope); } /** @@ -158,7 +199,7 @@ char* accountToJSONStringWithoutCredentials(const struct oidc_account* p) { } cJSON* _accountToJSON(const struct oidc_account* p, int useCredentials) { - cJSON* redirect_uris = listToJSONArray(account_getRedirectUris(p)); + cJSON* redirect_uris = stringListToJSONArray(account_getRedirectUris(p)); cJSON* json = generateJSONObject( AGENT_KEY_SHORTNAME, cJSON_String, strValid(account_getName(p)) ? account_getName(p) : "", @@ -322,23 +363,28 @@ char* defineUsableScopes(const struct oidc_account* account) { return usable; } -void account_setOSDefaultCertPath(struct oidc_account* account) { +char* getOSDefaultCertPath() { #ifdef __MSYS__ char* path = oidc_strcopy(CERT_FILE()); strReplaceChar(path, '\\', '/'); if (fileDoesExist(path)) { - account_setCertPath(account, path); - return; - } else { - secFree(path); + return path; } + secFree(path); #else for (unsigned int i = 0; i < sizeof(possibleCertFiles) / sizeof(*possibleCertFiles); i++) { if (fileDoesExist(possibleCertFiles[i])) { - account_setCertPath(account, oidc_strcopy(possibleCertFiles[i])); - return; + return oidc_strcopy(possibleCertFiles[i]); } } #endif + return NULL; +} + +char* getDefaultCertPath() { + if (getAgentConfig()->cert_path) { + return oidc_strcopy(getAgentConfig()->cert_path); + } + return getOSDefaultCertPath(); } diff --git a/src/account/account.h b/src/account/account.h index cba3bc01..bc8bbf81 100644 --- a/src/account/account.h +++ b/src/account/account.h @@ -55,18 +55,20 @@ void _secFreeAccount(struct oidc_account* p); void secFreeAccountContent(struct oidc_account* p); struct oidc_account* updateAccountWithPublicClientInfo(struct oidc_account*); +struct oidc_account* updateAccountWithUserClientInfo(struct oidc_account*); char* getScopesForPublicClient(const struct oidc_account*); +char* getScopesForUserClient(const struct oidc_account*); int accountConfigExists(const char* accountname); char* getAccountNameList(list_t* accounts); int hasRedirectUris(const struct oidc_account* account); -int account_matchByState(const struct oidc_account* p1, +int account_matchByState(const struct oidc_account* p1, + const struct oidc_account* p2); +int account_matchByName(const struct oidc_account* p1, const struct oidc_account* p2); -int account_matchByName(const struct oidc_account* p1, - const struct oidc_account* p2); -int account_matchByIssuerUrl(const struct oidc_account* p1, - const struct oidc_account* p2); -void account_setOSDefaultCertPath(struct oidc_account* account); +int account_matchByIssuerUrl(const struct oidc_account* p1, + const struct oidc_account* p2); +char* getDefaultCertPath(); // make setters and getters avialable #include "account/setandget.h" diff --git a/src/account/issuer_helper.c b/src/account/issuer_helper.c index 0f33dc2a..bc8ac883 100644 --- a/src/account/issuer_helper.c +++ b/src/account/issuer_helper.c @@ -6,8 +6,7 @@ #include "defines/agent_values.h" #include "defines/msys.h" #include "defines/oidc_values.h" -#include "defines/settings.h" -#include "utils/file_io/file_io.h" +#include "utils/config/issuerConfig.h" #include "utils/file_io/oidc_file_io.h" #include "utils/json.h" #include "utils/listUtils.h" @@ -136,104 +135,20 @@ int compIssuerUrls(const char* a, const char* b) { } void printIssuerHelp(const char* url) { - char* fileContent = NULL; - const char* const etcIssuerFile = -#ifdef ANY_MSYS - ETC_ISSUER_CONFIG_FILE(); -#else - ETC_ISSUER_CONFIG_FILE; -#endif - if (fileDoesExist(etcIssuerFile)) { - // Read the etc version by default, we have put some additional info there, - // usually this won't be the case for the user space one. - fileContent = readFile(etcIssuerFile); - } else { - // Read the user space issuer.config only if there is no etc version. This - // might be the case when a user installed the agent entirely in the user - // space. - fileContent = readOidcFile(ISSUER_CONFIG_FILENAME); - } - if (fileContent == NULL) { + const struct issuerConfig* c = getIssuerConfig(url); + if (c == NULL) { + printStdout("Unfortunately no contact information were found for " + "issuer '%s'\n", + url); return; } - char* elem = strtok(fileContent, "\n"); - while (elem != NULL) { - char* space = strchr(elem, ' '); - if (space) { - *space = '\0'; - } - if (compIssuerUrls(url, elem)) { - if (space) { - char* reg_uri = space + 1; - space = strchr(reg_uri, ' '); - char* contact = NULL; - if (space) { - *space = '\0'; - contact = space + 1; - } - if (strValid(reg_uri)) { - printStdout("You can try to register a client manually at '%s'\n", - reg_uri); - } - if (strValid(contact)) { - printStdout("You can contact the OpenID Provider at '%s'\n", contact); - } - } else { - printStdout("Unfortunately no contact information were found for " - "issuer '%s'\n", - url); - } - break; - } - elem = strtok(NULL, "\n"); - } - secFree(fileContent); -} - -list_t* getSuggestableIssuers() { - list_t* issuers = list_new(); - issuers->free = (void(*)(void*)) & _secFree; - issuers->match = (matchFunction)compIssuerUrls; - - char* fileContent = readOidcFile(ISSUER_CONFIG_FILENAME); - if (fileContent) { - char* elem = strtok(fileContent, "\n"); - while (elem != NULL) { - char* space = strchr(elem, ' '); - if (space) { - *space = '\0'; - } - if (findInList(issuers, elem) == NULL) { - list_rpush(issuers, list_node_new(oidc_strcopy(elem))); - } - elem = strtok(NULL, "\n"); - } - secFree(fileContent); + if (strValid(c->manual_register)) { + printStdout("You can try to register a client manually at '%s'\n", + c->manual_register); } - - fileContent = readFile( -#ifdef ANY_MSYS - ETC_ISSUER_CONFIG_FILE() -#else - ETC_ISSUER_CONFIG_FILE -#endif - ); - if (fileContent) { - char* elem = strtok(fileContent, "\n"); - while (elem != NULL) { - char* space = strchr(elem, ' '); - if (space) { - *space = '\0'; - } - if (findInList(issuers, elem) == NULL) { - list_rpush(issuers, list_node_new(oidc_strcopy(elem))); - } - elem = strtok(NULL, "\n"); - } - secFree(fileContent); + if (strValid(c->contact)) { + printStdout("You can contact the OpenID Provider at '%s'\n", c->contact); } - - return issuers; } size_t getFavIssuer(const struct oidc_account* account, list_t* suggestable) { diff --git a/src/account/issuer_helper.h b/src/account/issuer_helper.h index 8c51dfde..ac57f388 100644 --- a/src/account/issuer_helper.h +++ b/src/account/issuer_helper.h @@ -4,10 +4,9 @@ #include "account.h" #include "wrapper/list.h" -list_t* getSuggestableIssuers(); -size_t getFavIssuer(const struct oidc_account* account, list_t* suggestable); -void printSuggestIssuer(list_t* suggestable); -void printIssuerHelp(const char* url); +size_t getFavIssuer(const struct oidc_account* account, list_t* suggestable); +void printSuggestIssuer(list_t* suggestable); +void printIssuerHelp(const char* url); char* getUsableResponseTypes(const struct oidc_account* account, list_t* flows); char* getUsableGrantTypes(const struct oidc_account* account, list_t* flows); int compIssuerUrls(const char* a, const char* b); diff --git a/src/account/setandget.c b/src/account/setandget.c index dd77b90e..4edb2f6f 100644 --- a/src/account/setandget.c +++ b/src/account/setandget.c @@ -1,5 +1,6 @@ #include "setandget.h" +#include "utils/config/agent_config.h" #include "utils/hostname.h" #include "utils/string/stringUtils.h" @@ -110,6 +111,12 @@ unsigned long account_getTokenExpiresAt(const struct oidc_account* p) { char* account_getCertPath(const struct oidc_account* p) { return p ? p->cert_path : NULL; } +char* account_getCertPathOrDefault(const struct oidc_account* p) { + if (account_getCertPath(p)) { + return oidc_strcopy(account_getCertPath(p)); + } + return getDefaultCertPath(); +} list_t* account_getRedirectUris(const struct oidc_account* p) { return p ? p->redirect_uris : NULL; diff --git a/src/account/setandget.h b/src/account/setandget.h index 469b98c4..891ff604 100644 --- a/src/account/setandget.h +++ b/src/account/setandget.h @@ -29,6 +29,7 @@ char* account_getRefreshToken(const struct oidc_account* p); char* account_getAccessToken(const struct oidc_account* p); unsigned long account_getTokenExpiresAt(const struct oidc_account* p); char* account_getCertPath(const struct oidc_account* p); +char* account_getCertPathOrDefault(const struct oidc_account* p); list_t* account_getRedirectUris(const struct oidc_account* p); size_t account_getRedirectUrisCount(const struct oidc_account* p); char* account_getUsedState(const struct oidc_account* p); diff --git a/src/api/accounts.c b/src/api/accounts.c index 9ac7d6ab..5fcf74ac 100644 --- a/src/api/accounts.c +++ b/src/api/accounts.c @@ -6,10 +6,10 @@ #include "utils/json.h" #include "utils/logger.h" #include "utils/oidc_error.h" -#include "utils/printer.h" #include "utils/string/stringUtils.h" -struct agent_response parseForAgentLoadedAccountsListResponse(char* response) { +static struct agent_response parseForAgentAccountInfosResponse(char* response, + int type) { struct agent_response res; if (response == NULL) { res.type = AGENT_RESPONSE_TYPE_ERROR; @@ -18,7 +18,7 @@ struct agent_response parseForAgentLoadedAccountsListResponse(char* response) { return res; } - INIT_KEY_VALUE(IPC_KEY_STATUS, OIDC_KEY_ERROR, "info"); + INIT_KEY_VALUE(IPC_KEY_STATUS, OIDC_KEY_ERROR, IPC_KEY_INFO); if (CALL_GETJSONVALUES(response) < 0) { secFree(response); SEC_FREE_KEY_VALUES(); @@ -42,14 +42,21 @@ struct agent_response parseForAgentLoadedAccountsListResponse(char* response) { return res; } else { secFree(_status); - oidc_errno = OIDC_SUCCESS; - res.type = AGENT_RESPONSE_TYPE_ACCOUNTS; - res.loaded_accounts_response.accounts = _info; + oidc_errno = OIDC_SUCCESS; + res.type = type; + switch (type) { + case AGENT_RESPONSE_TYPE_ACCOUNTS: + res.loaded_accounts_response.accounts = _info; + break; + case AGENT_RESPONSE_TYPE_ACCOUNTINFO: + res.account_info_response.account_info = _info; + break; + } return res; } } -char* getLoadedAccountsListRequest() { +static char* getLoadedAccountsListRequest() { START_APILOGLEVEL cJSON* json = generateJSONObject(IPC_KEY_REQUEST, cJSON_String, REQUEST_VALUE_LOADEDACCOUNTS, NULL); @@ -60,22 +67,37 @@ char* getLoadedAccountsListRequest() { return ret; } -struct agent_response _getAgentLoadedAccountsListFromRequest( - unsigned char remote, const char* ipc_request) { - char* response = communicate(remote, ipc_request); - return parseForAgentLoadedAccountsListResponse(response); +static char* getAccountInfoRequest() { + START_APILOGLEVEL + cJSON* json = generateJSONObject(IPC_KEY_REQUEST, cJSON_String, + REQUEST_VALUE_ACCOUNTINFO, NULL); + char* ret = jsonToStringUnformatted(json); + secFreeJson(json); + logger(DEBUG, "%s", ret); + END_APILOGLEVEL + return ret; } -struct agent_response getAgentLoadedAccountsListResponse() { +static struct agent_response _getAgentAccountInfosResponse(int type) { START_APILOGLEVEL - char* request = getLoadedAccountsListRequest(); - struct agent_response ret = - _getAgentLoadedAccountsListFromRequest(LOCAL_COMM, request); + char* request; + switch (type) { + case AGENT_RESPONSE_TYPE_ACCOUNTS: + request = getLoadedAccountsListRequest(); + break; + case AGENT_RESPONSE_TYPE_ACCOUNTINFO: + request = getAccountInfoRequest(); + break; + default: return (struct agent_response){}; + } + char* response = communicate(LOCAL_COMM, request); + struct agent_response ret = parseForAgentAccountInfosResponse(response, type); struct oidc_error_state* localError = saveErrorState(); const unsigned char remote = _checkLocalResponseForRemote(ret); if (remote) { + response = communicate(remote, request); struct agent_response retRemote = - _getAgentLoadedAccountsListFromRequest(remote, request); + parseForAgentAccountInfosResponse(response, type); if (retRemote.type == AGENT_RESPONSE_TYPE_ERROR) { restoreErrorState(localError); secFreeAgentResponse(retRemote); @@ -90,6 +112,14 @@ struct agent_response getAgentLoadedAccountsListResponse() { return ret; } +struct agent_response getAgentLoadedAccountsListResponse() { + return _getAgentAccountInfosResponse(AGENT_RESPONSE_TYPE_ACCOUNTS); +} + +struct agent_response getAgentAccountInfoResponse() { + return _getAgentAccountInfosResponse(AGENT_RESPONSE_TYPE_ACCOUNTINFO); +} + char* getLoadedAccountsList() { struct agent_response response = getAgentLoadedAccountsListResponse(); if (response.type == AGENT_RESPONSE_TYPE_ACCOUNTS) { @@ -98,3 +128,12 @@ char* getLoadedAccountsList() { secFreeAgentResponse(response); return NULL; } + +char* getAccountInfo() { + struct agent_response response = getAgentAccountInfoResponse(); + if (response.type == AGENT_RESPONSE_TYPE_ACCOUNTINFO) { + return response.account_info_response.account_info; + } + secFreeAgentResponse(response); + return NULL; +} diff --git a/src/api/accounts.h b/src/api/accounts.h index b920b318..dfec98da 100644 --- a/src/api/accounts.h +++ b/src/api/accounts.h @@ -20,4 +20,21 @@ LIB_PUBLIC char* getLoadedAccountsList(); */ LIB_PUBLIC struct agent_response getAgentLoadedAccountsListResponse(); +/** + * @brief gets json object with information about all accounts + * @return a pointer to string with json info. Has to be freed after usage + * using + * @c secFree function. On failure @c NULL is returned and @c oidc_errno is set. + */ +LIB_PUBLIC char* getAccountInfo(); + +/** + * @brief gets json object with information about all accounts + * @return an agent_response struct containing a string representation of a json + * object holding information about all accounts. Has to be freed after usage + * using the @c secFreeAgentResponse function. On failure @c NULL is returned + * and @c oidc_errno is set. + */ +LIB_PUBLIC struct agent_response getAgentAccountInfoResponse(); + #endif // OIDC_ADD_API_H diff --git a/src/api/api.h b/src/api/api.h index a497bb3a..cd2e09d0 100644 --- a/src/api/api.h +++ b/src/api/api.h @@ -1,13 +1,11 @@ #ifndef OIDC_AGENT_API_H #define OIDC_AGENT_API_H +#include "accounts.h" #include "error.h" #include "memory.h" #include "mytokens.h" #include "response.h" #include "tokens.h" -#if defined __MINGW32__ || defined __MINGW64__ || defined _WIN32 -#include "accounts.h" -#endif #endif // OIDC_AGENT_API_H diff --git a/src/api/response.c b/src/api/response.c index d29a55f6..6843a999 100644 --- a/src/api/response.c +++ b/src/api/response.c @@ -6,7 +6,8 @@ #include "utils/printer.h" #include "utils/string/stringUtils.h" -unsigned char _checkLocalResponseForRemote(struct agent_response res) { // lgtm [cpp/large-parameter] +unsigned char _checkLocalResponseForRemote( + struct agent_response res) { // lgtm [cpp/large-parameter] if (res.type == AGENT_RESPONSE_TYPE_TOKEN && res.token_response.token != NULL) { return LOCAL_COMM; @@ -56,7 +57,14 @@ void secFreeLoadedAccountsListResponse( END_APILOGLEVEL } -void secFreeAgentResponse(struct agent_response agent_response) { // lgtm [cpp/large-parameter] +void secFreeAccountInfoResponse(struct account_info_response response) { + START_APILOGLEVEL + secFree(response.account_info); + END_APILOGLEVEL +} + +void secFreeAgentResponse( + struct agent_response agent_response) { // lgtm [cpp/large-parameter] START_APILOGLEVEL switch (agent_response.type) { case AGENT_RESPONSE_TYPE_ERROR: @@ -72,6 +80,9 @@ void secFreeAgentResponse(struct agent_response agent_response) { // lgtm [cpp/l case AGENT_RESPONSE_TYPE_MYTOKEN: secFreeMytokenResponse(agent_response.mytoken_response); break; + case AGENT_RESPONSE_TYPE_ACCOUNTINFO: + secFreeAccountInfoResponse(agent_response.account_info_response); + break; } END_APILOGLEVEL } diff --git a/src/api/response.h b/src/api/response.h index 46a160b7..9da2ef23 100644 --- a/src/api/response.h +++ b/src/api/response.h @@ -26,10 +26,20 @@ LIB_PUBLIC struct agent_error_response { }; /** - * @struct loaded_accounts_response oidc-add/api.h + * @struct loaded_accounts_response response.h * @brief a struct holding loaded accounts list as a string with delimiters */ -LIB_PUBLIC struct loaded_accounts_response { char* accounts; }; +LIB_PUBLIC struct loaded_accounts_response { + char* accounts; +}; + +/** + * @struct account_info_response response.h + * @brief a struct holding loaded accounts list as a string with delimiters + */ +LIB_PUBLIC struct account_info_response { + char* account_info; +}; /** * @struct mytoken_response response.h @@ -51,6 +61,7 @@ LIB_PUBLIC struct mytoken_response { #define AGENT_RESPONSE_TYPE_TOKEN 1 #define AGENT_RESPONSE_TYPE_ACCOUNTS 2 #define AGENT_RESPONSE_TYPE_MYTOKEN 3 +#define AGENT_RESPONSE_TYPE_ACCOUNTINFO 4 /** * @struct agent_response response.h @@ -63,6 +74,7 @@ LIB_PUBLIC struct agent_response { struct agent_error_response error_response; struct loaded_accounts_response loaded_accounts_response; struct mytoken_response mytoken_response; + struct account_info_response account_info_response; }; }; @@ -91,6 +103,12 @@ LIB_PUBLIC void secFreeErrorResponse(struct agent_error_response); LIB_PUBLIC void secFreeLoadedAccountsListResponse( struct loaded_accounts_response); +/** + * @brief clears and frees a loaded_accounts_response struct + * @param loaded_accounts_response the struct to be freed + */ +LIB_PUBLIC void secFreeAccountInfoResponse(struct account_info_response); + /** * @brief clears and frees an agent_response struct * @param agent_response the struct to be freed diff --git a/src/api/tokens.c b/src/api/tokens.c index 5472e45b..90448a66 100644 --- a/src/api/tokens.c +++ b/src/api/tokens.c @@ -113,7 +113,7 @@ struct agent_response _getAgentResponseFromRequest(unsigned char remote, } struct token_response _agentResponseToTokenResponse( - struct agent_response agentResponse) { // lgtm [cpp/large-parameter] + struct agent_response agentResponse) { // lgtm [cpp/large-parameter] if (agentResponse.type == AGENT_RESPONSE_TYPE_TOKEN) { return agentResponse.token_response; } @@ -121,27 +121,6 @@ struct token_response _agentResponseToTokenResponse( return (struct token_response){NULL, NULL, 0}; } -struct token_response getTokenResponse(const char* accountname, - time_t min_valid_period, - const char* scope, - const char* application_hint) { - return getTokenResponse3(accountname, min_valid_period, scope, - application_hint, NULL); -} - -struct token_response getTokenResponse3(const char* accountname, - time_t min_valid_period, - const char* scope, - const char* application_hint, - const char* audience) { - START_APILOGLEVEL - struct agent_response res = getAgentTokenResponse( - accountname, min_valid_period, scope, application_hint, audience); - struct token_response ret = _agentResponseToTokenResponse(res); - END_APILOGLEVEL - return ret; -} - struct agent_response getAgentTokenResponse(const char* accountname, time_t min_valid_period, const char* scope, @@ -170,27 +149,6 @@ struct agent_response getAgentTokenResponse(const char* accountname, return res; } -struct token_response getTokenResponseForIssuer(const char* issuer_url, - time_t min_valid_period, - const char* scope, - const char* application_hint) { - return getTokenResponseForIssuer3(issuer_url, min_valid_period, scope, - application_hint, NULL); -} - -struct token_response getTokenResponseForIssuer3(const char* issuer_url, - time_t min_valid_period, - const char* scope, - const char* application_hint, - const char* audience) { - START_APILOGLEVEL - struct agent_response res = getAgentTokenResponseForIssuer( - issuer_url, min_valid_period, scope, application_hint, audience); - struct token_response ret = _agentResponseToTokenResponse(res); - END_APILOGLEVEL - return ret; -} - struct agent_response getAgentTokenResponseForIssuer( const char* issuer_url, time_t min_valid_period, const char* scope, const char* application_hint, const char* audience) { @@ -204,44 +162,37 @@ struct agent_response getAgentTokenResponseForIssuer( } char* getAccessToken(const char* accountname, time_t min_valid_period, - const char* scope) { - return getAccessToken2(accountname, min_valid_period, scope, NULL); -} - -char* getAccessToken2(const char* accountname, time_t min_valid_period, - const char* scope, const char* application_hint) { - return getAccessToken3(accountname, min_valid_period, scope, application_hint, - NULL); -} - -char* getAccessToken3(const char* accountname, time_t min_valid_period, - const char* scope, const char* application_hint, - const char* audience) { + const char* scope, const char* application_hint, + const char* audience) { START_APILOGLEVEL - struct token_response response = getTokenResponse3( + struct agent_response response = getAgentTokenResponse( accountname, min_valid_period, scope, application_hint, audience); - secFree(response.issuer); + if (response.type == AGENT_RESPONSE_TYPE_ERROR) { + oidc_seterror(response.error_response.error); + oidc_errno = OIDC_EERROR; + secFreeAgentResponse(response); + return NULL; + } + char* at = oidc_strcopy(response.token_response.token); + secFreeAgentResponse(response); END_APILOGLEVEL - return response.token; + return at; } char* getAccessTokenForIssuer(const char* issuer_url, time_t min_valid_period, - const char* scope, const char* application_hint) { + const char* scope, const char* application_hint, + const char* audience) { START_APILOGLEVEL - struct token_response response = getTokenResponseForIssuer3( - issuer_url, min_valid_period, scope, application_hint, NULL); - secFree(response.issuer); - END_APILOGLEVEL - return response.token; -} - -char* getAccessTokenForIssuer3(const char* issuer_url, time_t min_valid_period, - const char* scope, const char* application_hint, - const char* audience) { - START_APILOGLEVEL - struct token_response response = getTokenResponseForIssuer3( + struct agent_response response = getAgentTokenResponseForIssuer( issuer_url, min_valid_period, scope, application_hint, audience); - secFree(response.issuer); + if (response.type == AGENT_RESPONSE_TYPE_ERROR) { + oidc_seterror(response.error_response.error); + oidc_errno = OIDC_EERROR; + secFreeAgentResponse(response); + return NULL; + } + char* at = oidc_strcopy(response.token_response.token); + secFreeAgentResponse(response); END_APILOGLEVEL - return response.token; + return at; } diff --git a/src/api/tokens.h b/src/api/tokens.h index 86aef0a9..a80c54e6 100644 --- a/src/api/tokens.h +++ b/src/api/tokens.h @@ -52,43 +52,6 @@ LIB_PUBLIC struct agent_response getAgentTokenResponseForIssuer( const char* issuer_url, time_t min_valid_period, const char* scope, const char* application_hint, const char* audience); -/** - * @brief gets a valid access token for an account config - * @deprecated use @c getTokenResponse3 instead to additionally get the - * issuer_url and expiration date for the returned access token or if only the - * access token is required @c getAccessToken3 - * @param accountname the short name of the account config for which an access - * token should be returned - * @param min_valid_period the minium period of time the access token has to be - * valid in seconds - * @param scope a space delimited list of scope values for the to be issued - * access token. @c NULL if default value for that account configuration should - * be used. - * @return a pointer to the access token. Has to be freed after usage using - * @c secFree function. On failure @c NULL is returned and @c oidc_errno is set. - */ -LIB_PUBLIC char* getAccessToken(const char* accountname, - time_t min_valid_period, const char* scope); - -/** - * @brief gets a valid access token for an account config - * @deprecated use @c getAccessToken3 or @c getTokenResponse3 instead - * @param accountname the short name of the account config for which an access - * token should be returned - * @param min_valid_period the minium period of time the access token has to be - * valid in seconds - * @param scope a space delimited list of scope values for the to be issued - * access token. @c NULL if default value for the used account configuration - * should be used. - * @param application_hint a hint indicating what application requests the - * access token. This string might be displayed to the user. - * @return a pointer to the access token. Has to be freed after usage using - * @c secFree function. On failure @c NULL is returned and @c oidc_errno is set. - */ -LIB_PUBLIC char* getAccessToken2(const char* accountname, - time_t min_valid_period, const char* scope, - const char* application_hint); - /** * @brief gets a valid access token for an account config * @param accountname the short name of the account config for which an access @@ -106,31 +69,10 @@ LIB_PUBLIC char* getAccessToken2(const char* accountname, * @return a pointer to the access token. Has to be freed after usage using * @c secFree function. On failure @c NULL is returned and @c oidc_errno is set. */ -LIB_PUBLIC char* getAccessToken3(const char* accountname, - time_t min_valid_period, const char* scope, - const char* application_hint, - const char* audience); - -/** - * @brief gets a valid access token for an account config - * @deprecated use @c getAccessTokenForIssuer3 or @c getTokenResponseForIssuer3 - * instead - * @param issuer_url the issuer url of the provider for which an access token - * should be returned - * @param min_valid_period the minium period of time the access token has to be - * valid in seconds - * @param scope a space delimited list of scope values for the to be issued - * access token. @c NULL if default value for that account configuration should - * be used. - * @param application_hint a hint indicating what application requests the - * access token. This string might be displayed to the user. - * @return a pointer to the access token. Has to be freed after usage using - * @c secFree function. On failure @c NULL is returned and @c oidc_errno is set. - */ -LIB_PUBLIC char* getAccessTokenForIssuer(const char* issuer_url, - time_t min_valid_period, - const char* scope, - const char* application_hint); +LIB_PUBLIC char* getAccessToken(const char* accountname, + time_t min_valid_period, const char* scope, + const char* application_hint, + const char* audience); /** * @brief gets a valid access token for an account config @@ -149,109 +91,10 @@ LIB_PUBLIC char* getAccessTokenForIssuer(const char* issuer_url, * @return a pointer to the access token. Has to be freed after usage using * @c secFree function. On failure @c NULL is returned and @c oidc_errno is set. */ -LIB_PUBLIC char* getAccessTokenForIssuer3(const char* issuer_url, - time_t min_valid_period, - const char* scope, - const char* application_hint, - const char* audience); - -/** - * @brief gets a valid access token for an account config as well as related - * information - * @deprecated use @c getTokenResponse3 instead - * @param accountname the short name of the account config for which an access - * token should be returned - * @param min_valid_period the minium period of time the access token has to be - * valid in seconds - * @param scope a space delimited list of scope values for the to be issued - * access token. @c NULL if default value for that account configuration should - * be used. - * @param application_hint a hint indicating what application requests the - * access token. This string might be displayed to the user. - * @return a token_response struct containing the access token, issuer_url, and - * expiration time. - * Has to be freed after usage using the @c secFreeTokenResponse function. On - * failure a zeroed struct is returned and @c oidc_errno is set. - */ -LIB_PUBLIC struct token_response getTokenResponse(const char* accountname, - time_t min_valid_period, - const char* scope, - const char* application_hint); - -/** - * @brief gets a valid access token for an account config as well as related - * information - * @deprecated use @c getAgentTokenResponse instead to additionally get an error - * and help message on failure - * @param accountname the short name of the account config for which an access - * token should be returned - * @param min_valid_period the minium period of time the access token has to be - * valid in seconds - * @param scope a space delimited list of scope values for the to be issued - * access token. @c NULL if default value for that account configuration should - * be used. - * @param application_hint a hint indicating what application requests the - * access token. This string might be displayed to the user. - * @param audience Use this parameter to request an access token with this - * specific audience. Can be a space separated list. @c NULL if no special - * audience should be requested. - * @return a token_response struct containing the access token, issuer_url, and - * expiration time. - * Has to be freed after usage using the @c secFreeTokenResponse function. On - * failure a zeroed struct is returned and @c oidc_errno is set. - */ -LIB_PUBLIC struct token_response getTokenResponse3(const char* accountname, - time_t min_valid_period, - const char* scope, - const char* application_hint, - const char* audience); - -/** - * @brief gets a valid access token for a specific provider as well as related - * information - * @deprecated use @c getTokenResponseForIssuer3 instead - * @param issuer_url the issuer url of the provider for which an access token - * should be returned - * @param min_valid_period the minium period of time the access token has to be - * valid in seconds - * @param scope a space delimited list of scope values for the to be issued - * access token. @c NULL if default value for the used account configuration - * should be used. - * @param application_hint a hint indicating what application requests the - * access token. This string might be displayed to the user. - * @return a token_response struct containing the access token, issuer_url, and - * expiration time. - * Has to be freed after usage using the @c secFreeTokenResponse function. On - * failure a zeroed struct is returned and @c oidc_errno is set. - */ -LIB_PUBLIC struct token_response getTokenResponseForIssuer( - const char* issuer_url, time_t min_valid_period, const char* scope, - const char* application_hint); - -/** - * @brief gets a valid access token for a specific provider as well as related - * information - * @deprecated use @c getAgentTokenResponseForIssuer instead to additionally get - * an error and help message on failure - * @param issuer_url the issuer url of the provider for which an access token - * should be returned - * @param min_valid_period the minium period of time the access token has to be - * valid in seconds - * @param scope a space delimited list of scope values for the to be issued - * access token. @c NULL if default value for the used account configuration - * should be used. - * @param application_hint a hint indicating what application requests the - * access token. This string might be displayed to the user. - * @param audience Use this parameter to request an access token with this - * specific audience. Can be a space separated list. @c NULL if no special - * audience should be requested. - * @return a token_response struct containing the access token, issuer_url, and - * expiration time. - * Has to be freed after usage using the @c secFreeTokenResponse function. On - * failure a zeroed struct is returned and @c oidc_errno is set. - */ -LIB_PUBLIC struct token_response getTokenResponseForIssuer3( - const char* issuer_url, time_t min_valid_period, const char* scope, - const char* application_hint, const char* audience); +LIB_PUBLIC char* getAccessTokenForIssuer(const char* issuer_url, + time_t min_valid_period, + const char* scope, + const char* application_hint, + const char* audience); #endif // OIDC_TOKEN_API_TOKENS_H diff --git a/src/defines/agent_values.h b/src/defines/agent_values.h index 72df62e5..d2f0c877 100644 --- a/src/defines/agent_values.h +++ b/src/defines/agent_values.h @@ -12,6 +12,46 @@ #define AGENT_KEY_USESPUBCLIENT "uses_pub_client" #define AGENT_KEY_MYTOKENURL "mytoken_url" #define AGENT_KEY_MYTOKENPROFILE "mytoken_profile" +#define AGENT_KEY_PUBCLIENT "pubclient" +#define AGENT_KEY_CONTACT "contact" +#define AGENT_KEY_MANUAL_CLIENT_REGISTRATION_URI "register" +#define AGENT_KEY_PWSTORE "pw-store" +#define AGENT_KEY_DEFAULT_ACCOUNT "default_account" +#define AGENT_KEY_ACCOUNTS "accounts" + +#define CONFIG_KEY_AGENT "oidc-agent" +#define CONFIG_KEY_ADD "oidc-add" +#define CONFIG_KEY_GEN "oidc-gen" +#define CONFIG_KEY_CLIENT "oidc-token" +#define CONFIG_KEY_BINDADDRESS "bind_address" +#define CONFIG_KEY_CONFIRM "confirm" +#define CONFIG_KEY_AUTOLOAD "autoload" +#define CONFIG_KEY_AUTOGEN "auto-gen" +#define CONFIG_KEY_AUTOGENSCOPEMODE "auto-gen-scope-mode" +#define CONFIG_VALUE_SCOPEMODE_EXACT "exact" +#define CONFIG_VALUE_SCOPEMODE_MAX AGENT_SCOPE_ALL +#define CONFIG_KEY_AUTOREAUTH "auto-reauthenticate" +#define CONFIG_KEY_WEBSERVER "webserver" +#define CONFIG_KEY_CUSTOMURISCHEME "custom-uri-scheme" +#define CONFIG_KEY_DEBUGLOGGING "debug_logging" +#define CONFIG_KEY_GROUP "group" +#define CONFIG_KEY_CNID "cnid" +#define CONFIG_KEY_AUTOOPENURL "auto-open-url" +#define CONFIG_KEY_DEFAULTGPGKEY "default_gpg_key" +#define CONFIG_KEY_PROMPTMODE "prompt" +#define CONFIG_KEY_PWPROMPTMODE "pw-prompt" +#define CONFIG_KEY_ANSWERCONFIRMPROMPTS "answer-confirm-prompts" +#define CONFIG_KEY_DEFAULTMYTOKENSERVER "default_mytoken_server" +#define CONFIG_KEY_DEFAULTMYTOKENPROFILE "default_mytoken_profile" +#define CONFIG_KEY_PREFERMYTOKENOVEROIDC "prefer_mytoken_over_oidc" +#define CONFIG_KEY_STOREPW "store-pw" +#define CONFIG_KEY_DEFAULTMINLIFETIME "default-min-lifetime" +#define CONFIG_KEY_STATSCOLLECT "stats_collect_local" +#define CONFIG_KEY_STATSCOLLECTSHARE "stats_collect_share" +#define CONFIG_KEY_STATSCOLLECTLOCATION "stats_collect_location" +#define CONFIG_KEY_LEGACYAUDMODE "legacy_aud_mode" + +#define ACCOUNTINFO_KEY_HASPUBCLIENT "pubclient" // INTERNAL / CLI FLOW VALUES #define FLOW_VALUE_CODE "code" diff --git a/src/defines/ipc_values.h b/src/defines/ipc_values.h index 884986ef..352dc636 100644 --- a/src/defines/ipc_values.h +++ b/src/defines/ipc_values.h @@ -22,7 +22,7 @@ #define IPC_KEY_MINVALID "min_valid_period" #define IPC_KEY_PASSWORDENTRY "pw_entry" #define IPC_KEY_CONFIRM "confirm" -#define IPC_KEY_ALWAYSALLOWID "always_allow_id" +#define IPC_KEY_ALWAYSALLOWID "always_allow_id_token" #define IPC_KEY_REDIRECTEDURI "redirect_uri" #define IPC_KEY_FROMGEN "from_gen" #define IPC_KEY_USECUSTOMSCHEMEURL "no_webserver" @@ -71,6 +71,7 @@ #define REQUEST_VALUE_FILEREMOVE "file_remove" #define REQUEST_VALUE_DELETECLIENT "delete_client" #define REQUEST_VALUE_REAUTHENTICATE "reauthenticate" +#define REQUEST_VALUE_ACCOUNTINFO "account_info" // RESPONSE TEMPLATES #define RESPONSE_SUCCESS "{\"" IPC_KEY_STATUS "\":\"" STATUS_SUCCESS "\"}" @@ -218,17 +219,27 @@ // internal communication (between oidcp and oidcd) #define INT_REQUEST_VALUE_UPD_REFRESH "update_refresh" +#define INT_REQUEST_VALUE_UPD_ISSUER "update_issuer" #define INT_REQUEST_VALUE_AUTOLOAD "autoload" +#define INT_REQUEST_VALUE_AUTOGEN "autogen" #define INT_REQUEST_VALUE_CONFIRM "confirm" #define INT_REQUEST_VALUE_CONFIRMIDTOKEN "confirm_id" #define INT_REQUEST_VALUE_CONFIRMMYTOKEN "confirm_mytoken" #define INT_REQUEST_VALUE_QUERY_ACCDEFAULT "query_account_default" #define INT_IPC_KEY_OIDCERRNO "oidc_errno" +#define INT_IPC_KEY_ACTION "action" + +#define INT_ACTION_VALUE_ADD "add" +#define INT_ACTION_VALUE_REMOVE "remove" #define INT_REQUEST_UPD_REFRESH \ "{\"" IPC_KEY_REQUEST "\":\"" INT_REQUEST_VALUE_UPD_REFRESH \ "\",\"" IPC_KEY_SHORTNAME "\":\"%s\",\"" OIDC_KEY_REFRESHTOKEN "\":\"%s\"}" +#define INT_REQUEST_UPD_ISSUER \ + "{\"" IPC_KEY_REQUEST "\":\"" INT_REQUEST_VALUE_UPD_ISSUER \ + "\",\"" IPC_KEY_ISSUERURL "\":\"%s\",\"" IPC_KEY_SHORTNAME \ + "\":\"%s\", \"" INT_IPC_KEY_ACTION "\":\"%s\"}" #define INT_REQUEST_AUTOLOAD \ "{\"" IPC_KEY_REQUEST "\":\"" INT_REQUEST_VALUE_AUTOLOAD \ "\",\"" IPC_KEY_SHORTNAME "\":\"%s\",\"" IPC_KEY_APPLICATIONHINT \ @@ -237,6 +248,10 @@ "{\"" IPC_KEY_REQUEST "\":\"" INT_REQUEST_VALUE_AUTOLOAD \ "\",\"" IPC_KEY_SHORTNAME "\":\"%s\",\"" IPC_KEY_ISSUERURL \ "\":\"%s\",\"" IPC_KEY_APPLICATIONHINT "\":\"%s\"}" +#define INT_REQUEST_AUTOGEN \ + "{\"" IPC_KEY_REQUEST "\":\"" INT_REQUEST_VALUE_AUTOGEN \ + "\",\"" IPC_KEY_ISSUERURL "\":\"%s\",\"" OIDC_KEY_SCOPE \ + "\":\"%s\",\"" IPC_KEY_APPLICATIONHINT "\":\"%s\"}" #define INT_REQUEST_CONFIRM \ "{\"" IPC_KEY_REQUEST "\":\"%s\",\"" IPC_KEY_SHORTNAME \ "\":\"%s\",\"" IPC_KEY_APPLICATIONHINT "\":\"%s\"}" @@ -250,8 +265,6 @@ #define INT_REQUEST_QUERY_ACCDEFAULT_ISSUER \ "{\"" IPC_KEY_REQUEST "\":\"" INT_REQUEST_VALUE_QUERY_ACCDEFAULT \ "\",\"" IPC_KEY_ISSUERURL "\":\"%s\"}" -#define INT_REQUEST_QUERY_ACCDEFAULT \ - "{\"" IPC_KEY_REQUEST "\":\"" INT_REQUEST_VALUE_QUERY_ACCDEFAULT "\"}" #define INT_RESPONSE_ACCDEFAULT \ "{\"" IPC_KEY_STATUS "\":\"" STATUS_SUCCESS "\",\"" IPC_KEY_SHORTNAME \ "\":\"%s\"}" diff --git a/src/defines/oidc_values.h b/src/defines/oidc_values.h index 065fa59a..0d48aaf2 100644 --- a/src/defines/oidc_values.h +++ b/src/defines/oidc_values.h @@ -32,6 +32,7 @@ #define OIDC_KEY_RESPONSETYPE "response_type" #define OIDC_KEY_SCOPE "scope" #define OIDC_KEY_AUDIENCE "audience" +#define OIDC_KEY_RESOURCE "resource" #define GOOGLE_KEY_ACCESSTYPE "access_type" // AUTH CODE FLOW #define OIDC_KEY_REDIRECTURI "redirect_uri" diff --git a/src/defines/settings.c b/src/defines/settings.c index 9e754414..ed5c8282 100644 --- a/src/defines/settings.c +++ b/src/defines/settings.c @@ -12,13 +12,14 @@ // one is appended later #endif -char* _config_path = NULL; -char* _cert_file = NULL; -char* _etc_issuer_config_file = NULL; -char* _etc_pubclients_config_file = NULL; -char* _etc_mytoken_base = NULL; +char* _config_path = NULL; +char* _cert_file = NULL; +char* _etc_issuer_config_file = NULL; +char* _etc_issuer_config_dir = NULL; +char* _etc_config_file = NULL; +char* _etc_mytoken_base = NULL; -const char* config_path() { +static const char* config_path() { if (_config_path == NULL) { _config_path = fillEnvVarsInPath(CONFIG_PATH); } @@ -40,12 +41,19 @@ const char* ETC_ISSUER_CONFIG_FILE() { return _etc_issuer_config_file; } -const char* ETC_PUBCLIENTS_CONFIG_FILE() { - if (_etc_pubclients_config_file == NULL) { - _etc_pubclients_config_file = - oidc_pathcat(config_path(), "oidc-agent/" PUBCLIENTS_FILENAME); +const char* ETC_ISSUER_CONFIG_DIR() { + if (_etc_issuer_config_dir == NULL) { + _etc_issuer_config_dir = + oidc_pathcat(config_path(), "oidc-agent/" ISSUER_CONFIG_DIRNAME); } - return _etc_pubclients_config_file; + return _etc_issuer_config_dir; +} + +const char* ETC_CONFIG_FILE() { + if (_etc_config_file == NULL) { + _etc_config_file = oidc_pathcat(config_path(), "oidc-agent/config"); + } + return _etc_config_file; } const char* _MYTOKEN_GLOBAL_BASE() { diff --git a/src/defines/settings.h b/src/defines/settings.h index 438e480e..c3856e81 100644 --- a/src/defines/settings.h +++ b/src/defines/settings.h @@ -22,6 +22,11 @@ * location */ #define OIDC_CONFIG_DIR_ENV_NAME "OIDC_CONFIG_DIR" +/** + * the name of the environment variable that might hold the path to the user's + * config file + */ +#define OIDC_USER_CONFIG_PATH_ENV_NAME "OIDC_USER_CONFIG" /** * the scope used as default value */ @@ -33,13 +38,14 @@ // file names #define ISSUER_CONFIG_FILENAME "issuer.config" -#define PUBCLIENTS_FILENAME "pubclients.config" +#define ISSUER_CONFIG_DIRNAME ISSUER_CONFIG_FILENAME ".d" #ifdef ANY_MSYS const char* CERT_FILE(); const char* ETC_ISSUER_CONFIG_FILE(); -const char* ETC_PUBCLIENTS_CONFIG_FILE(); +const char* ETC_ISSUER_CONFIG_DIR(); const char* _MYTOKEN_GLOBAL_BASE(); +const char* ETC_CONFIG_FILE(); #define OIDC_AGENT_REGISTRY "SOFTWARE\\oidc-agent" #define SOCKET_LOOPBACK_ADDRESS "127.0.0.1" @@ -49,8 +55,8 @@ const char* _MYTOKEN_GLOBAL_BASE(); #endif #define ETC_ISSUER_CONFIG_FILE CONFIG_PATH "/oidc-agent/" ISSUER_CONFIG_FILENAME -#define ETC_PUBCLIENTS_CONFIG_FILE \ - CONFIG_PATH "/oidc-agent/" PUBCLIENTS_FILENAME +#define ETC_ISSUER_CONFIG_DIR CONFIG_PATH "/oidc-agent/" ISSUER_CONFIG_DIRNAME +#define ETC_CONFIG_FILE CONFIG_PATH "/oidc-agent/config" #endif #define MAX_PASS_TRIES 3 diff --git a/src/ipc/serveripc.c b/src/ipc/serveripc.c index b8bbcefb..eb927c4f 100644 --- a/src/ipc/serveripc.c +++ b/src/ipc/serveripc.c @@ -7,18 +7,20 @@ #include #include #include +#include #include -#include #include #include "cryptIpc.h" #include "defines/ipc_values.h" +#include "defines/msys.h" #include "ipc.h" #include "ipc/cryptCommunicator.h" #include "utils/agentLogger.h" #include "utils/db/connection_db.h" #include "utils/file_io/fileUtils.h" #include "utils/file_io/file_io.h" +#include "utils/file_io/safefile/check_file_path.h" #include "utils/json.h" #include "utils/logger.h" #include "utils/memory.h" @@ -128,7 +130,7 @@ char* create_passed_socket_path(const char* requested_path) { oidc_ipc_dir = oidc_strcopy(requested_path); if (lastChar(oidc_ipc_dir) == '/') { // only dir specified lastChar(oidc_ipc_dir) = '\0'; - } else { // full path including file specified + } else { // full path including file specified char* lastSlash = strrchr(oidc_ipc_dir, '/'); socket_file = oidc_strcopy(lastSlash + 1); char* tmp = oidc_strncopy(oidc_ipc_dir, lastSlash - oidc_ipc_dir); @@ -206,6 +208,13 @@ oidc_error_t ipc_server_init(struct connection* con, const char* group_name, strcpy(con->server->sun_path, path); secFree(path); server_socket_path = con->server->sun_path; + +#ifndef ANY_MSYS + if (check_socket_path(oidc_ipc_dir, group_name) != OIDC_SUCCESS) { + return oidc_errno; + } +#endif + return OIDC_SUCCESS; } @@ -235,9 +244,10 @@ char* getServerSocketPath() { return server_socket_path; } * @param con, a pointer to the connection struct * @return @c 0 on success or an errorcode on failure */ -int ipc_bindAndListen(struct connection* con) { +int ipc_bindAndListen(struct connection* con, const char* group) { logger(DEBUG, "binding ipc\n"); unlink(con->server->sun_path); + mode_t previous_mask = umask(group ? 0117 : 0177); if (bind(*(con->sock), (struct sockaddr*)con->server, sizeof(struct sockaddr_un))) { logger(ALERT, "binding stream socket: %m"); @@ -245,6 +255,12 @@ int ipc_bindAndListen(struct connection* con) { oidc_errno = OIDC_EBIND; return OIDC_EBIND; } +#ifndef ANY_MSYS + if (check_socket_path(con->server->sun_path, group) != OIDC_SUCCESS) { + return oidc_errno; + } +#endif + umask(previous_mask); int flags; if (-1 == (flags = fcntl(*(con->sock), F_GETFL, 0))) flags = 0; diff --git a/src/ipc/serveripc.h b/src/ipc/serveripc.h index 3c020cf7..f54bbebb 100644 --- a/src/ipc/serveripc.h +++ b/src/ipc/serveripc.h @@ -17,7 +17,7 @@ char* getServerSocketPath(); oidc_error_t ipc_server_init(struct connection* con, const char* group_name, const char* socket_path); oidc_error_t ipc_initWithPath(struct connection* con); -int ipc_bindAndListen(struct connection* con); +int ipc_bindAndListen(struct connection* con, const char* group); void server_ipc_freeLastKey(); char* server_ipc_read(const int); diff --git a/src/oidc-add/add_handler.c b/src/oidc-add/add_handler.c index ece2a233..6b877249 100644 --- a/src/oidc-add/add_handler.c +++ b/src/oidc-add/add_handler.c @@ -2,16 +2,17 @@ #include -#include "account/account.h" #include "defines/ipc_values.h" #include "ipc/cryptCommunicator.h" #include "oidc-add/parse_ipc.h" #include "utils/accountUtils.h" +#include "utils/config/issuerConfig.h" #include "utils/file_io/oidc_file_io.h" +#include "utils/json.h" #include "utils/password_entry.h" #include "utils/printer.h" -#include "utils/prompt.h" -#include "utils/promptUtils.h" +#include "utils/prompting/prompt.h" +#include "utils/prompting/promptUtils.h" #include "utils/string/stringUtils.h" time_t getPWExpiresInDependingOn(struct arguments* arguments) { @@ -42,6 +43,12 @@ void add_handleAdd(char* account, struct arguments* arguments) { oidc_perror(); exit(EXIT_FAILURE); } + char* iss = getJSONValueFromString(json_p, OIDC_KEY_ISSUER); + if (iss == NULL) { + iss = getJSONValueFromString(json_p, AGENT_KEY_ISSUERURL); + } + const struct issuerConfig* iss_c = getIssuerConfig(iss); + secFree(iss); char* password = result.password; struct password_entry pw = {.shortname = account}; @@ -50,17 +57,13 @@ void add_handleAdd(char* account, struct arguments* arguments) { pwe_setCommand(&pw, arguments->pw_cmd); type |= PW_TYPE_CMD; } - if (arguments->pw_lifetime.argProvided && password) { + unsigned char storePW = + arguments->pw_lifetime.argProvided || (iss_c && iss_c->store_pw); + if (storePW && password) { pwe_setPassword(&pw, password); pwe_setExpiresIn(&pw, getPWExpiresInDependingOn(arguments)); type |= PW_TYPE_MEM; } - if (arguments->pw_keyring) { - if (pw.password == NULL) { // Only set password if not already done - pwe_setPassword(&pw, password); - } - type |= PW_TYPE_MNG; - } if (arguments->pw_env) { if (pw.password == NULL) { pwe_setPassword(&pw, password); @@ -71,16 +74,12 @@ void add_handleAdd(char* account, struct arguments* arguments) { pwe_setFile(&pw, arguments->pw_file); type |= PW_TYPE_FILE; } - if (arguments->pw_gpg) { - pwe_setGPGKey(&pw, arguments->pw_gpg); - type |= PW_TYPE_GPG; - } pwe_setType(&pw, type); char* pw_str = passwordEntryToJSONString(&pw); secFree(password); char* res = NULL; - if (arguments->lifetime.argProvided) { + if (storePW) { res = ipc_cryptCommunicate(arguments->remote, REQUEST_ADD_LIFETIME, json_p, arguments->lifetime.lifetime, pw_str, arguments->confirm, @@ -106,7 +105,7 @@ void add_handleRemoveAll(struct arguments* arguments) { } void add_handleLock(int lock, struct arguments* arguments) { - char* password = promptPassword("Enter lock password: ", "Password", NULL, + char* password = promptPassword("Enter lock password", "Password", NULL, CLI_PROMPT_VERBOSE); if (password == NULL) { oidc_perror(); @@ -117,8 +116,8 @@ void add_handleLock(int lock, struct arguments* arguments) { res = ipc_cryptCommunicate(arguments->remote, REQUEST_LOCK, REQUEST_VALUE_UNLOCK, password); } else { // locking agent - char* passwordConfirm = promptPassword( - "Confirm lock password: ", "Password", NULL, CLI_PROMPT_VERBOSE); + char* passwordConfirm = promptPassword("Confirm lock password", "Password", + NULL, CLI_PROMPT_VERBOSE); if (!strequal(password, passwordConfirm)) { printError("Passwords do not match.\n"); secFree(password); diff --git a/src/oidc-add/oidc-add_options.c b/src/oidc-add/oidc-add_options.c index 52e21d0c..65165326 100644 --- a/src/oidc-add/oidc-add_options.c +++ b/src/oidc-add/oidc-add_options.c @@ -2,18 +2,17 @@ #include "defines/settings.h" #include "utils/commonFeatures.h" -#include "utils/prompt_mode.h" +#include "utils/config/add_config.h" +#include "utils/prompting/prompt_mode.h" #include "utils/string/stringUtils.h" #define OPT_PW_STORE 2 -#define OPT_PW_KEYRING 3 #define OPT_PW_CMD 4 #define OPT_ALWAYS_ALLOW_IDTOKEN 5 #define OPT_PW_PROMPT 6 #define OPT_PW_FILE 7 #define OPT_REMOTE 8 #define OPT_PW_ENV 9 -#define OPT_PW_GPG 10 static struct argp_option options[] = { {0, 0, 0, 0, "General:", 1}, @@ -34,10 +33,6 @@ static struct argp_option options[] = { "Keeps the encryption password encrypted in memory for TIME seconds. " "Default value for TIME: Forever", 1}, -#ifndef __APPLE__ - {"pw-keyring", OPT_PW_KEYRING, 0, 0, - "Stores the used encryption password in the systems' keyring", 1}, -#endif {"pw-env", OPT_PW_ENV, OIDC_PASSWORD_ENV_NAME, OPTION_ARG_OPTIONAL, "Reads the encryption password from the passed environment variable " "(default: " OIDC_PASSWORD_ENV_NAME "), instead of prompting the user", @@ -46,11 +41,6 @@ static struct argp_option options[] = { "Command from which the agent can read the encryption password", 1}, {"pw-file", OPT_PW_FILE, "FILE", 0, "Uses the first line of FILE as the encryption password.", 1}, - {"pw-gpg", OPT_PW_GPG, "KEY_ID", 0, - "Uses the passed GPG KEY for encryption", 1}, - {"pw-pgp", OPT_PW_GPG, "KEY_ID", OPTION_ALIAS, NULL, 1}, - {"gpg", OPT_PW_GPG, "KEY_ID", OPTION_ALIAS, NULL, 1}, - {"pgp", OPT_PW_GPG, "KEY_ID", OPTION_ALIAS, NULL, 1}, {"confirm", 'c', 0, 0, "Require user confirmation when an application requests an access token " "for this configuration", @@ -94,8 +84,6 @@ static error_t parse_opt(int key, char* arg, struct argp_state* state) { case OPT_PW_ENV: arguments->pw_env = arg ?: OIDC_PASSWORD_ENV_NAME; break; case OPT_PW_CMD: arguments->pw_cmd = arg; break; case OPT_PW_FILE: arguments->pw_file = arg; break; - case OPT_PW_GPG: arguments->pw_gpg = arg; break; - case OPT_PW_KEYRING: arguments->pw_keyring = 1; break; case OPT_PW_PROMPT: if (strequal(arg, "cli")) { arguments->pw_prompt_mode = PROMPT_MODE_CLI; @@ -159,29 +147,28 @@ static char doc[] = struct argp argp = {options, parse_opt, args_doc, doc, 0, 0, 0}; void initArguments(struct arguments* arguments) { - arguments->remove = 0; - arguments->removeAll = 0; - arguments->debug = 0; - arguments->verbose = 0; - arguments->listConfigured = 0; - arguments->listLoaded = 0; - arguments->print = 0; - arguments->lifetime.argProvided = 0; + arguments->remove = 0; + arguments->removeAll = 0; + arguments->debug = getAddConfig()->debug; + arguments->verbose = 0; + arguments->listConfigured = 0; + arguments->listLoaded = 0; + arguments->print = 0; + arguments->lifetime.argProvided = + getAddConfig()->store_pw ? ARG_PROVIDED_BUT_USES_DEFAULT : 0; arguments->lifetime.lifetime = 0; arguments->lock = 0; arguments->unlock = 0; arguments->args[0] = NULL; arguments->pw_lifetime.argProvided = 0; arguments->pw_lifetime.lifetime = 0; - arguments->pw_keyring = 0; arguments->pw_cmd = NULL; arguments->pw_file = NULL; - arguments->pw_gpg = NULL; arguments->pw_env = NULL; arguments->confirm = 0; arguments->always_allow_idtoken = 0; arguments->remote = 0; arguments->force = 0; - arguments->pw_prompt_mode = PROMPT_MODE_CLI; + arguments->pw_prompt_mode = getAddConfig()->pw_prompt_mode; set_pw_prompt_mode(arguments->pw_prompt_mode); } diff --git a/src/oidc-add/oidc-add_options.h b/src/oidc-add/oidc-add_options.h index 6250c735..83f27c0e 100644 --- a/src/oidc-add/oidc-add_options.h +++ b/src/oidc-add/oidc-add_options.h @@ -12,7 +12,6 @@ struct arguments { char* pw_cmd; char* pw_file; char* pw_env; - char* pw_gpg; unsigned char remove; unsigned char removeAll; @@ -23,7 +22,6 @@ struct arguments { unsigned char print; unsigned char lock; unsigned char unlock; - unsigned char pw_keyring; unsigned char confirm; unsigned char always_allow_idtoken; unsigned char pw_prompt_mode; diff --git a/src/oidc-agent-service/oidc-agent-service b/src/oidc-agent-service/oidc-agent-service index 9ebb662b..ca542702 100755 --- a/src/oidc-agent-service/oidc-agent-service +++ b/src/oidc-agent-service/oidc-agent-service @@ -1,17 +1,17 @@ #!/bin/bash +set -e + TMP="${TMPDIR:-/tmp}" -AGENTSERVICEDIR=${TMP}/oidc-agent-service/${UID} +AGENTSERVICEDIR=${TMP}/oidc-agent-service-${UID} PID_FILE=$AGENTSERVICEDIR/oidc-agent.pid SOCK=$AGENTSERVICEDIR/oidc-agent.sock JQ=jq CAT=/bin/cat ECHO=/bin/echo -LN=/bin/ln RM=/bin/rm -MKDIR=/bin/mkdir REALPATH=realpath OIDC_INCLUDE @@ -20,7 +20,7 @@ function stop() { if [ -f "$PID_FILE" ]; then OIDCD_PID=$($CAT $PID_FILE) OIDC_SOCK=$($REALPATH $SOCK) - $OIDC_AGENT --kill + $OIDC_AGENT --kill 2>/dev/null $ECHO "unset OIDCD_PID_FILE;" $RM -rf $AGENTSERVICEDIR fi @@ -34,12 +34,9 @@ function echo_vars() { } function start() { - json=$($OIDC_AGENT $OIDC_AGENT_OPTS --json) + json=$($OIDC_AGENT -a $SOCK $OIDC_AGENT_OPTS --json) OIDCD_PID=$($ECHO "$json" | $JQ -r ".dpid") - OIDC_SOCK=$($ECHO "$json" | $JQ -r ".socket") - $MKDIR -p $AGENTSERVICEDIR $ECHO $OIDCD_PID > $PID_FILE - $LN -sf $OIDC_SOCK $SOCK echo_vars } @@ -101,8 +98,12 @@ if [[ "$1" == "start" ]]; then fi if [[ "$1" == "restart" ]]; then if [ "x${OIDC_AGENT_RESTART_WITH_SAME_OPTS}" == "xTrue" ]; then - STATUS=$($OIDC_AGENT --status --json) - OIDC_AGENT_OPTS=$($ECHO "$STATUS" | $JQ -r ".command_line_options") + set +e + STATUS=$($OIDC_AGENT --status --json 2>/dev/null) + if [ $? ]; then + OIDC_AGENT_OPTS=$($ECHO "$STATUS" | $JQ -r ".command_line_options") + fi + set -e fi stop start diff --git a/src/oidc-agent/daemonize.c b/src/oidc-agent/daemonize.c index 54d0f1b7..4647224a 100644 --- a/src/oidc-agent/daemonize.c +++ b/src/oidc-agent/daemonize.c @@ -10,15 +10,7 @@ #include "utils/agentLogger.h" -void sig_handler(int signo) { - switch (signo) { - case SIGSEGV: agent_log(EMERGENCY, "Caught Signal SIGSEGV"); break; - default: agent_log(EMERGENCY, "Caught Signal %d", signo); - } - exit(signo); -} - -pid_t daemonize() { +pid_t daemonize(unsigned char deep) { fflush(stdout); // flush before forking, otherwise the buffered content is // printed multiple times. (Only relevant when stdout is // redirected, tty is line-buffered.) @@ -28,18 +20,23 @@ pid_t daemonize() { agent_log(ALERT, "fork %m"); exit(EXIT_FAILURE); } else if (pid > 0) { - exit(EXIT_SUCCESS); + if (deep) { + exit(EXIT_SUCCESS); + } + return pid; + } + if (deep) { + if ((pid = fork()) == -1) { + agent_log(ALERT, "fork %m"); + exit(EXIT_FAILURE); + } else if (pid > 0) { + return pid; + } } if (setsid() < 0) { exit(EXIT_FAILURE); } signal(SIGHUP, SIG_IGN); - if ((pid = fork()) == -1) { - agent_log(ALERT, "fork %m"); - exit(EXIT_FAILURE); - } else if (pid > 0) { - return pid; - } if (chdir("/") != 0) { agent_log(ERROR, "chdir %m"); } diff --git a/src/oidc-agent/daemonize.h b/src/oidc-agent/daemonize.h index 006b4a49..2336ccb5 100644 --- a/src/oidc-agent/daemonize.h +++ b/src/oidc-agent/daemonize.h @@ -3,6 +3,6 @@ #include -pid_t daemonize(); +pid_t daemonize(unsigned char deep); #endif // AGENT_DAEMONIZING_H diff --git a/src/oidc-agent/http/http.c b/src/oidc-agent/http/http.c index 95ca527c..c72ecb88 100644 --- a/src/oidc-agent/http/http.c +++ b/src/oidc-agent/http/http.c @@ -112,7 +112,6 @@ char* _httpsPOST(const char* url, const char* data, struct curl_slist* headers, pass; } else { secFree(s.ptr); - cleanup(curl); return NULL; } } diff --git a/src/oidc-agent/http/http_handler.c b/src/oidc-agent/http/http_handler.c index 88fd1c6b..5c81e1cd 100644 --- a/src/oidc-agent/http/http_handler.c +++ b/src/oidc-agent/http/http_handler.c @@ -3,6 +3,7 @@ #include #include +#include "account/account.h" #include "defines/version.h" #include "http_errorHandler.h" #include "utils/agentLogger.h" @@ -52,7 +53,7 @@ oidc_error_t curlMemInit() { * @return a CURL pointer */ CURL* init() { - if (curlMemInit()!=OIDC_SUCCESS) { + if (curlMemInit() != OIDC_SUCCESS) { return NULL; } @@ -70,6 +71,8 @@ CURL* init() { return curl; } +char* _tmp_cert_path = NULL; + /** @fn void setSSLOpts(CURL* curl) * @brief sets SSL options * @param curl the curl instance @@ -79,6 +82,11 @@ void setSSLOpts(CURL* curl, const char* cert_file) { curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 1L); if (cert_file) { curl_easy_setopt(curl, CURLOPT_CAINFO, cert_file); + } else { + // use default cert path + secFree(_tmp_cert_path); + _tmp_cert_path = getDefaultCertPath(); + curl_easy_setopt(curl, CURLOPT_CAINFO, _tmp_cert_path); } } @@ -147,7 +155,7 @@ void setBasicAuth(CURL* curl, const char* username, const char* password) { * @return 0 on success, for error values see \f CURLErrorHandling */ oidc_error_t perform(CURL* curl) { - // curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L); + curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L); // curl_easy_setopt(curl, CURLOPT_POSTREDIR, CURL_REDIR_POST_ALL); CURLcode res; unsigned int tries = 0; diff --git a/src/oidc-agent/lock_state.c b/src/oidc-agent/lock_state.c index 5c94b79b..eb86eedb 100644 --- a/src/oidc-agent/lock_state.c +++ b/src/oidc-agent/lock_state.c @@ -22,6 +22,13 @@ oidc_error_t unlock(const char* password) { if (!strequal(agent_state.lock_state.hash, hash)) { secFree(hash); oidc_errno = OIDC_EPASS; + if (fail_count < 25) { + fail_count++; + } + unsigned int delay = 100 * fail_count * fail_count; + agent_log(DEBUG, "unlock failed, delaying %0.1lf seconds", + (double)delay / 1000); + msleep(delay); return oidc_errno; } secFree(hash); @@ -33,14 +40,7 @@ oidc_error_t unlock(const char* password) { agent_log(DEBUG, "Agent unlocked"); return OIDC_SUCCESS; } - /* delay in 0.1s increments up to 10s */ - if (fail_count < 100) { - fail_count++; - } - unsigned int delay = 100 * fail_count; - agent_log(DEBUG, "unlock failed, delaying %0.1lf seconds", - (double)delay / 1000); - msleep(delay); + return oidc_errno; } diff --git a/src/oidc-agent/mytoken/oidc_flow.c b/src/oidc-agent/mytoken/oidc_flow.c index 01750746..40d14351 100644 --- a/src/oidc-agent/mytoken/oidc_flow.c +++ b/src/oidc-agent/mytoken/oidc_flow.c @@ -42,9 +42,11 @@ struct oidc_device_code* initMytokenOIDCFlow(struct oidc_account* account) { return NULL; } agent_log(DEBUG, "Data to send: %s", data); - char* res = sendJSONPostWithoutBasicAuth(mytoken_endpoint, data, - account_getCertPath(account), NULL); + char* cert_path = account_getCertPathOrDefault(account); + char* res = + sendJSONPostWithoutBasicAuth(mytoken_endpoint, data, cert_path, NULL); secFree(data); + secFree(cert_path); if (res == NULL) { return NULL; } @@ -80,9 +82,10 @@ oidc_error_t lookUpMytokenPollingCode(struct oidc_account* account, return oidc_errno; } agent_log(DEBUG, "Data to send: %s", data); - char* res = - sendJSONPostWithoutBasicAuth(account_getMytokenEndpoint(account), data, - account_getCertPath(account), NULL); + char* cert_path = account_getCertPathOrDefault(account); + char* res = sendJSONPostWithoutBasicAuth(account_getMytokenEndpoint(account), + data, cert_path, NULL); + secFree(cert_path); secFree(data); if (res == NULL) { return oidc_errno; diff --git a/src/oidc-agent/mytoken/submytoken.c b/src/oidc-agent/mytoken/submytoken.c index 69cef9f9..31fddf26 100644 --- a/src/oidc-agent/mytoken/submytoken.c +++ b/src/oidc-agent/mytoken/submytoken.c @@ -17,6 +17,7 @@ #include "utils/oidc_error.h" #include "utils/parseJson.h" #include "utils/string/stringUtils.h" +#include "wrapper/cjson.h" char* get_submytoken(struct ipcPipe pipes, struct oidc_account* account, const char* profile, const char* application_hint) { @@ -48,8 +49,10 @@ char* get_submytoken(struct ipcPipe pipes, struct oidc_account* account, } agent_log(DEBUG, "Data to send: %s", data); char* consent_endpoint = oidc_pathcat(account_getMytokenUrl(account), "c"); - char* consent = sendJSONPostWithoutBasicAuth( - consent_endpoint, data, account_getCertPath(account), NULL); + char* cert_path = account_getCertPathOrDefault(account); + char* consent = + sendJSONPostWithoutBasicAuth(consent_endpoint, data, cert_path, NULL); + secFree(cert_path); secFree(consent_endpoint); secFree(data); if (consent == NULL) { @@ -81,8 +84,9 @@ char* get_submytoken(struct ipcPipe pipes, struct oidc_account* account, secFree(json); agent_log(DEBUG, "Sending updated data: %s", updated_data); - char* res = sendJSONPostWithoutBasicAuth(mytoken_endpoint, updated_data, - account_getCertPath(account), NULL); + char* res = + sendJSONPostWithoutBasicAuth(mytoken_endpoint, updated_data, + account_getCertPathOrDefault(account), NULL); if (res == NULL) { return NULL; } diff --git a/src/oidc-agent/oidc-agent_options.c b/src/oidc-agent/oidc-agent_options.c index 58898f2a..11c443a5 100644 --- a/src/oidc-agent/oidc-agent_options.c +++ b/src/oidc-agent/oidc-agent_options.c @@ -1,11 +1,12 @@ #include "oidc-agent_options.h" #include "utils/agentLogger.h" +#include "utils/config/agent_config.h" +#include "utils/listUtils.h" #include "utils/string/stringUtils.h" #define OPT_NOAUTOLOAD 2 #define OPT_NO_WEBSERVER 3 -#define OPT_PW_STORE 4 #define OPT_GROUP 5 #define OPT_NO_SCHEME 6 #define OPT_LOG_CONSOLE 7 @@ -16,24 +17,24 @@ #define OPT_NO_AUTOREAUTHENTICATE 12 void initArguments(struct arguments* arguments) { - arguments->kill_flag = 0; - arguments->console = 0; - arguments->debug = 0; - arguments->lifetime = 0; - arguments->no_autoload = 0; - arguments->confirm = 0; - arguments->no_webserver = 0; - arguments->pw_lifetime.lifetime = 0; - arguments->pw_lifetime.argProvided = 0; - arguments->group = NULL; - arguments->socket_path = NULL; - arguments->no_scheme = 0; - arguments->always_allow_idtoken = 0; - arguments->log_console = 0; - arguments->status = 0; - arguments->json = 0; - arguments->quiet = 0; - arguments->no_autoreauthenticate = 0; + arguments->kill_flag = 0; + arguments->console = 0; + arguments->debug = getAgentConfig()->debug; + arguments->lifetime = getAgentConfig()->lifetime; + arguments->no_autoload = !getAgentConfig()->autoload; + arguments->confirm = getAgentConfig()->confirm; + arguments->no_webserver = !getAgentConfig()->webserver; + arguments->group = getAgentConfig()->group; + arguments->socket_path = getAgentConfig()->bind_address; + arguments->no_scheme = !getAgentConfig()->customurischeme; + arguments->always_allow_idtoken = getAgentConfig()->alwaysallowidtoken; + arguments->log_console = 0; + arguments->status = 0; + arguments->json = 0; + arguments->quiet = 0; + arguments->no_autoreauthenticate = !getAgentConfig()->autoreauth; + arguments->command = NULL; + arguments->args_list = NULL; } static struct argp_option options[] = { @@ -72,11 +73,6 @@ static struct argp_option options[] = { "authorization code flow is used. oidc-agent will not use a custom uri " "scheme redirect.", 1}, - {"pw-store", OPT_PW_STORE, "TIME", OPTION_ARG_OPTIONAL, - "Keeps the encryption passwords for all loaded account configurations " - "encrypted in memory for TIME seconds. Can be overwritten for a specific " - "configuration with oidc-add. Default value for TIME: Forever", - 1}, {"with-group", OPT_GROUP, "GROUP_NAME", OPTION_ARG_OPTIONAL, "This option allows that applications running under another user can " "access the agent. The user running the other application and the user " @@ -137,17 +133,22 @@ static error_t parse_opt(int key, char* arg __attribute__((unused)), } arguments->lifetime = strToInt(arg); break; - case OPT_PW_STORE: - arguments->pw_lifetime.argProvided = 1; - arguments->pw_lifetime.lifetime = strToULong(arg); - break; case OPT_JSON: arguments->json = 1; break; case OPT_QUIET: arguments->quiet = 1; break; case OPT_NO_AUTOREAUTHENTICATE: arguments->no_autoreauthenticate = 1; break; case 'h': argp_state_help(state, state->out_stream, ARGP_HELP_STD_HELP); break; - case ARGP_KEY_ARG: argp_usage(state); break; + case ARGP_KEY_ARG: + if (state->arg_num == 0) { + arguments->command = arg; + arguments->args_list = list_new(); + } + list_rpush(arguments->args_list, list_node_new(arg)); + break; + case ARGP_KEY_END: + arguments->args = (char* const*)listToArray(arguments->args_list); + break; default: return ARGP_ERR_UNKNOWN; } return 0; diff --git a/src/oidc-agent/oidc-agent_options.h b/src/oidc-agent/oidc-agent_options.h index 9b8e3481..f320fb2d 100644 --- a/src/oidc-agent/oidc-agent_options.h +++ b/src/oidc-agent/oidc-agent_options.h @@ -4,6 +4,7 @@ #include #include "utils/lifetimeArg.h" +#include "wrapper/list.h" struct arguments { unsigned char kill_flag; @@ -20,11 +21,14 @@ struct arguments { unsigned char quiet; unsigned char no_autoreauthenticate; - time_t lifetime; - struct lifetimeArg pw_lifetime; + time_t lifetime; - char* group; - char* socket_path; + const char* group; + const char* socket_path; + + const char* command; + list_t* args_list; + char* const* args; }; void initArguments(struct arguments* arguments); diff --git a/src/oidc-agent/oidc/flows/code.c b/src/oidc-agent/oidc/flows/code.c index 62268544..0afa0880 100644 --- a/src/oidc-agent/oidc/flows/code.c +++ b/src/oidc-agent/oidc/flows/code.c @@ -7,6 +7,7 @@ #include "oidc-agent/httpserver/startHttpserver.h" #include "oidc.h" #include "utils/agentLogger.h" +#include "utils/config/issuerConfig.h" #include "utils/crypt/crypt.h" #include "utils/listUtils.h" #include "utils/oidc/oidcUtils.h" @@ -42,9 +43,11 @@ oidc_error_t codeExchange(struct oidc_account* account, const char* code, return oidc_errno; } agent_log(DEBUG, "Data to send: %s", data); - char* res = sendPostDataWithBasicAuth( - account_getTokenEndpoint(account), data, account_getCertPath(account), - account_getClientId(account), account_getClientSecret(account)); + char* cert_path = account_getCertPathOrDefault(account); + char* res = sendPostDataWithBasicAuth(account_getTokenEndpoint(account), data, + cert_path, account_getClientId(account), + account_getClientSecret(account)); + secFree(cert_path); secFree(data); if (res == NULL) { return oidc_errno; @@ -130,13 +133,22 @@ char* buildCodeFlowUri(const struct oidc_account* account, char** state_ptr, secFree(*code_verifier_ptr); code_verifier_ptr = NULL; } + char* aud_tmp = NULL; if (strValid(account_getAudience(account))) { - list_rpush(postData, list_node_new(OIDC_KEY_AUDIENCE)); - list_rpush(postData, list_node_new(account_getAudience(account))); + const struct issuerConfig* iss_c = + getIssuerConfig(account_getIssuerUrl(account)); + if (iss_c && iss_c->legacy_aud_mode) { + list_rpush(postData, list_node_new(OIDC_KEY_AUDIENCE)); + list_rpush(postData, list_node_new(account_getAudience(account))); + } else { + aud_tmp = oidc_strcopy(account_getAudience(account)); + addAudienceRFC8707ToList(postData, aud_tmp); + } } char* uri_parameters = generatePostDataFromList(postData); secFree(code_challenge); secFree(scope); + secFree(aud_tmp); list_destroy(postData); char* uri = oidc_sprintf("%s?%s", auth_endpoint, uri_parameters); secFree(uri_parameters); diff --git a/src/oidc-agent/oidc/flows/device.c b/src/oidc-agent/oidc/flows/device.c index 8a7a2bc8..debca0a9 100644 --- a/src/oidc-agent/oidc/flows/device.c +++ b/src/oidc-agent/oidc/flows/device.c @@ -6,6 +6,7 @@ #include "oidc-agent/oidcd/deviceCodeEntry.h" #include "oidc.h" #include "utils/agentLogger.h" +#include "utils/config/issuerConfig.h" #include "utils/db/deviceCode_db.h" #include "utils/errorUtils.h" #include "utils/string/stringUtils.h" @@ -29,13 +30,21 @@ char* generateDeviceCodeLookupPostData(const struct oidc_account* a, list_rpush(postDataList, list_node_new(OIDC_GRANTTYPE_DEVICE)); list_rpush(postDataList, list_node_new(OIDC_KEY_DEVICECODE)); list_rpush(postDataList, list_node_new(tmp_devicecode)); + char* aud_tmp = NULL; if (strValid(account_getAudience(a))) { - list_rpush(postDataList, list_node_new(OIDC_KEY_AUDIENCE)); - list_rpush(postDataList, list_node_new(account_getAudience(a))); + const struct issuerConfig* iss_c = getIssuerConfig(account_getIssuerUrl(a)); + if (iss_c && iss_c->legacy_aud_mode) { + list_rpush(postDataList, list_node_new(OIDC_KEY_AUDIENCE)); + list_rpush(postDataList, list_node_new(account_getAudience(a))); + } else { + aud_tmp = oidc_strcopy(account_getAudience(a)); + addAudienceRFC8707ToList(postDataList, aud_tmp); + } } char* str = generatePostDataFromList(postDataList); list_destroy(postDataList); secFree(tmp_devicecode); + secFree(aud_tmp); return str; } @@ -52,9 +61,11 @@ struct oidc_device_code* initDeviceFlow(struct oidc_account* account) { return NULL; } agent_log(DEBUG, "Data to send: %s", data); - char* res = sendPostDataWithBasicAuth( - device_authorization_endpoint, data, account_getCertPath(account), - account_getClientId(account), account_getClientSecret(account)); + char* cert_path = account_getCertPathOrDefault(account); + char* res = sendPostDataWithBasicAuth(device_authorization_endpoint, data, + cert_path, account_getClientId(account), + account_getClientSecret(account)); + secFree(cert_path); secFree(data); if (res == NULL) { return NULL; @@ -93,9 +104,11 @@ oidc_error_t lookUpDeviceCode(struct oidc_account* account, return oidc_errno; } agent_log(DEBUG, "Data to send: %s", data); - char* res = sendPostDataWithBasicAuth( - account_getTokenEndpoint(account), data, account_getCertPath(account), - account_getClientId(account), account_getClientSecret(account)); + char* cert_path = account_getCertPathOrDefault(account); + char* res = sendPostDataWithBasicAuth(account_getTokenEndpoint(account), data, + cert_path, account_getClientId(account), + account_getClientSecret(account)); + secFree(cert_path); secFree(data); if (res == NULL) { return oidc_errno; diff --git a/src/oidc-agent/oidc/flows/oidc.c b/src/oidc-agent/oidc/flows/oidc.c index fc3d2003..71797c49 100644 --- a/src/oidc-agent/oidc/flows/oidc.c +++ b/src/oidc-agent/oidc/flows/oidc.c @@ -2,6 +2,7 @@ #include #include +#include #include #include "account/account.h" @@ -159,3 +160,15 @@ char* parseTokenResponse(const unsigned char mode, const char* res, return parseTokenResponseCallbacks(mode, res, a, &defaultErrorHandling, pipes, refreshFlow); } + +void addAudienceRFC8707ToList(list_t* postDataList, char* audience_cpy) { + if (audience_cpy == NULL || postDataList == NULL) { + return; + } + char* a = strtok(audience_cpy, " "); + while (a) { + list_rpush(postDataList, list_node_new(OIDC_KEY_RESOURCE)); + list_rpush(postDataList, list_node_new(a)); + a = strtok(NULL, " "); + } +} \ No newline at end of file diff --git a/src/oidc-agent/oidc/flows/oidc.h b/src/oidc-agent/oidc/flows/oidc.h index 13ca62aa..9b022051 100644 --- a/src/oidc-agent/oidc/flows/oidc.h +++ b/src/oidc-agent/oidc/flows/oidc.h @@ -20,5 +20,6 @@ char* parseTokenResponseCallbacks( unsigned char mode, const char* res, struct oidc_account* a, void (*errorHandling)(const char*, const char*), struct ipcPipe pipes, unsigned char refreshFlow); +void addAudienceRFC8707ToList(list_t* postDataList, char* audience_cpy); #endif // OIDC_H diff --git a/src/oidc-agent/oidc/flows/openid_config.c b/src/oidc-agent/oidc/flows/openid_config.c index ef439ce4..80b5f64c 100644 --- a/src/oidc-agent/oidc/flows/openid_config.c +++ b/src/oidc-agent/oidc/flows/openid_config.c @@ -12,8 +12,8 @@ #include "utils/oidc_error.h" #include "utils/string/stringUtils.h" -char* _getIssuerConfig(struct oidc_account* account) { - char* cert_path = account_getCertPath(account); +char* _obtainIssuerConfig(struct oidc_account* account) { + char* cert_path = account_getCertPathOrDefault(account); char* res = NULL; if (strValid(account_getConfigEndpoint(account))) { res = httpsGET(account_getConfigEndpoint(account), NULL, cert_path); @@ -44,19 +44,20 @@ char* _getIssuerConfig(struct oidc_account* account) { } account_setConfigEndpoint(account, configuration_endpoint); } + secFree(cert_path); agent_log(DEBUG, "Configuration endpoint is: %s", account_getConfigEndpoint(account)); return res; } -/** @fn oidc_error_t getIssuerConfig(struct oidc_account* account) +/** @fn oidc_error_t obtainIssuerConfig(struct oidc_account* account) * @brief retrieves issuer config from the configuration_endpoint * @note the issuer url has to be set prior * @param account the account struct, will be updated with the retrieved * config * @return a oidc_error status code */ -oidc_error_t getIssuerConfig(struct oidc_account* account) { - char* res = _getIssuerConfig(account); +oidc_error_t obtainIssuerConfig(struct oidc_account* account) { + char* res = _obtainIssuerConfig(account); if (NULL == res) { return oidc_errno; } @@ -70,10 +71,8 @@ char* getScopesSupportedFor(const char* issuer_url, const char* config_endpoint, account_setConfigEndpoint(&account, oidc_strcopy(config_endpoint)); if (strValid(cert_path)) { account_setCertPath(&account, oidc_strcopy(cert_path)); - } else { - account_setOSDefaultCertPath(&account); } - oidc_error_t err = getIssuerConfig(&account); + oidc_error_t err = obtainIssuerConfig(&account); if (err != OIDC_SUCCESS) { return NULL; } @@ -90,10 +89,8 @@ char* getProvidersSupportedByMytoken(const char* issuer_url, account_setConfigEndpoint(&account, oidc_strcopy(config_endpoint)); if (strValid(cert_path)) { account_setCertPath(&account, oidc_strcopy(cert_path)); - } else { - account_setOSDefaultCertPath(&account); } - char* res = _getIssuerConfig(&account); + char* res = _obtainIssuerConfig(&account); if (res == NULL) { return NULL; } diff --git a/src/oidc-agent/oidc/flows/openid_config.h b/src/oidc-agent/oidc/flows/openid_config.h index 201dc0cc..6d6f6f97 100644 --- a/src/oidc-agent/oidc/flows/openid_config.h +++ b/src/oidc-agent/oidc/flows/openid_config.h @@ -4,7 +4,7 @@ #include "account/account.h" #include "utils/oidc_error.h" -oidc_error_t getIssuerConfig(struct oidc_account* account); +oidc_error_t obtainIssuerConfig(struct oidc_account* account); char* getScopesSupportedFor(const char* issuer_url, const char* config_endpoint, const char* cert_path); char* getProvidersSupportedByMytoken(const char* issuer_url, diff --git a/src/oidc-agent/oidc/flows/password.c b/src/oidc-agent/oidc/flows/password.c index 95a72832..56c17ddc 100644 --- a/src/oidc-agent/oidc/flows/password.c +++ b/src/oidc-agent/oidc/flows/password.c @@ -7,6 +7,7 @@ #include "oidc-agent/http/http_ipc.h" #include "oidc.h" #include "utils/agentLogger.h" +#include "utils/config/issuerConfig.h" #include "utils/oidc_error.h" #include "utils/string/stringUtils.h" @@ -28,11 +29,19 @@ char* generatePasswordPostData(const struct oidc_account* a, list_rpush(postDataList, list_node_new((char*)scope ?: account_getScope(a))); } + char* aud_tmp = NULL; if (strValid(account_getAudience(a))) { - list_rpush(postDataList, list_node_new(OIDC_KEY_AUDIENCE)); - list_rpush(postDataList, list_node_new(account_getAudience(a))); + const struct issuerConfig* iss_c = getIssuerConfig(account_getIssuerUrl(a)); + if (iss_c && iss_c->legacy_aud_mode) { + list_rpush(postDataList, list_node_new(OIDC_KEY_AUDIENCE)); + list_rpush(postDataList, list_node_new(account_getAudience(a))); + } else { + aud_tmp = oidc_strcopy(account_getAudience(a)); + addAudienceRFC8707ToList(postDataList, aud_tmp); + } } char* str = generatePostDataFromList(postDataList); + secFree(aud_tmp); list_destroy(postDataList); return str; } @@ -51,9 +60,11 @@ oidc_error_t passwordFlow(struct oidc_account* p, struct ipcPipe pipes, ; } agent_log(DEBUG, "Data to send: %s", data); - char* res = sendPostDataWithBasicAuth( - account_getTokenEndpoint(p), data, account_getCertPath(p), - account_getClientId(p), account_getClientSecret(p)); + char* cert_path = account_getCertPathOrDefault(p); + char* res = sendPostDataWithBasicAuth(account_getTokenEndpoint(p), data, + cert_path, account_getClientId(p), + account_getClientSecret(p)); + secFree(cert_path); secFree(data); if (NULL == res) { return oidc_errno; diff --git a/src/oidc-agent/oidc/flows/refresh.c b/src/oidc-agent/oidc/flows/refresh.c index 5b552a45..15e723de 100644 --- a/src/oidc-agent/oidc/flows/refresh.c +++ b/src/oidc-agent/oidc/flows/refresh.c @@ -7,27 +7,19 @@ #include "oidc-agent/http/http_ipc.h" #include "oidc.h" #include "utils/agentLogger.h" +#include "utils/config/issuerConfig.h" #include "utils/string/stringUtils.h" char* generateRefreshPostData(const struct oidc_account* a, const char* scope, const char* audience) { char* refresh_token = account_getRefreshToken(a); char* scope_tmp = oidc_strcopy( - strValid(scope) ? scope - : account_getScope( - a)); // if scopes are explicitly set use these, if + strValid(scope) ? scope + : account_getScope( + a)); // if scopes are explicitly set use these, if // not we use the same as for the used refresh // token. Usually this parameter can be // omitted. For unity we have to include this. - char* aud_tmp = - oidc_strcopy(strValid(audience) - ? audience - : NULL); // account_getAudience(a)); // if audience is - // explicitly set use it, if not we use the - // default audience for this account. This is - // only needed if including audience changes - // not only the audience of the new AT, but - // also of the RT and therefore of future ATs. list_t* postDataList = list_new(); // list_rpush(postDataList, list_node_new(OIDC_KEY_CLIENTID)); // list_rpush(postDataList, list_node_new(account_getClientId(a))); @@ -37,14 +29,16 @@ char* generateRefreshPostData(const struct oidc_account* a, const char* scope, list_rpush(postDataList, list_node_new(OIDC_GRANTTYPE_REFRESH)); list_rpush(postDataList, list_node_new(OIDC_KEY_REFRESHTOKEN)); list_rpush(postDataList, list_node_new(refresh_token)); - if (!strValid(account_getClientSecret(a)) || - account_getUsesPubClient( - a)) { // In case of public client add client id to request - list_rpush(postDataList, list_node_new(OIDC_KEY_CLIENTID)); - list_rpush(postDataList, list_node_new(account_getClientId(a))); - if (strValid(account_getClientSecret(a))) { - list_rpush(postDataList, list_node_new(OIDC_KEY_CLIENTSECRET)); - list_rpush(postDataList, list_node_new(account_getClientSecret(a))); + if (account_getMytokenUrl(a) == NULL) { + if (!strValid(account_getClientSecret(a)) || + account_getUsesPubClient( + a)) { // In case of public client add client id to request + list_rpush(postDataList, list_node_new(OIDC_KEY_CLIENTID)); + list_rpush(postDataList, list_node_new(account_getClientId(a))); + if (strValid(account_getClientSecret(a))) { + list_rpush(postDataList, list_node_new(OIDC_KEY_CLIENTSECRET)); + list_rpush(postDataList, list_node_new(account_getClientSecret(a))); + } } } @@ -52,14 +46,21 @@ char* generateRefreshPostData(const struct oidc_account* a, const char* scope, list_rpush(postDataList, list_node_new(OIDC_KEY_SCOPE)); list_rpush(postDataList, list_node_new(scope_tmp)); } - if (strValid(aud_tmp)) { - list_rpush(postDataList, list_node_new(OIDC_KEY_AUDIENCE)); - list_rpush(postDataList, list_node_new(aud_tmp)); + char* aud_tmp = NULL; + if (strValid(audience)) { + aud_tmp = oidc_strcopy(audience); + const struct issuerConfig* iss_c = getIssuerConfig(account_getIssuerUrl(a)); + if (iss_c && iss_c->legacy_aud_mode) { + list_rpush(postDataList, list_node_new(OIDC_KEY_AUDIENCE)); + list_rpush(postDataList, list_node_new(aud_tmp)); + } else { + addAudienceRFC8707ToList(postDataList, aud_tmp); + } } char* str = generatePostDataFromList(postDataList); list_destroy(postDataList); - secFree(aud_tmp); secFree(scope_tmp); + secFree(aud_tmp); return str; } @@ -73,9 +74,11 @@ char* refreshFlow(unsigned char return_mode, struct oidc_account* p, ; } agent_log(DEBUG, "Data to send: %s", data); - char* res = sendPostDataWithBasicAuth( - account_getTokenEndpoint(p), data, account_getCertPath(p), - account_getClientId(p), account_getClientSecret(p)); + char* cert_path = account_getCertPathOrDefault(p); + char* res = sendPostDataWithBasicAuth(account_getTokenEndpoint(p), data, + cert_path, account_getClientId(p), + account_getClientSecret(p)); + secFree(cert_path); secFree(data); if (NULL == res) { return NULL; diff --git a/src/oidc-agent/oidc/flows/registration.c b/src/oidc-agent/oidc/flows/registration.c index 90d4248c..c1f3294d 100644 --- a/src/oidc-agent/oidc/flows/registration.c +++ b/src/oidc-agent/oidc/flows/registration.c @@ -72,10 +72,11 @@ char* _dynamicRegistration(struct oidc_account* account, list_t* flows, headers = curl_slist_append(headers, auth_header); secFree(auth_header); } - char* res = sendJSONPostWithBasicAuth( - account_getRegistrationEndpoint(account), body, - account_getCertPath(account), account_getClientId(account), - account_getClientSecret(account), headers); + char* cert_path = account_getCertPathOrDefault(account); + char* res = sendJSONPostWithBasicAuth( + account_getRegistrationEndpoint(account), body, cert_path, + account_getClientId(account), account_getClientSecret(account), headers); + secFree(cert_path); secFree(body); if (res == NULL) { return NULL; diff --git a/src/oidc-agent/oidc/flows/revoke.c b/src/oidc-agent/oidc/flows/revoke.c index 6f7086a8..820eb8f2 100644 --- a/src/oidc-agent/oidc/flows/revoke.c +++ b/src/oidc-agent/oidc/flows/revoke.c @@ -18,17 +18,18 @@ oidc_error_t _revokeToken(struct oidc_account* account, } char* refresh_token = account_getRefreshToken(account); char* data = generatePostData( - OIDC_KEY_TOKENTYPE_HINT, OIDC_TOKENTYPE_REFRESH, OIDC_KEY_TOKEN, - refresh_token, withClientId ? OIDC_KEY_CLIENTID : NULL, + OIDC_KEY_TOKENTYPE_HINT, OIDC_TOKENTYPE_REFRESH, OIDC_KEY_TOKEN, + refresh_token, withClientId ? OIDC_KEY_CLIENTID : NULL, withClientId ? account_getClientId(account) : NULL, NULL); if (data == NULL) { return oidc_errno; } agent_log(DEBUG, "Data to send: %s", data); - char* res = sendPostDataWithBasicAuth(account_getRevocationEndpoint(account), - data, account_getCertPath(account), - account_getClientId(account), - account_getClientSecret(account)); + char* cert_path = account_getCertPathOrDefault(account); + char* res = sendPostDataWithBasicAuth( + account_getRevocationEndpoint(account), data, cert_path, + account_getClientId(account), account_getClientSecret(account)); + secFree(cert_path); secFree(data); if (res == NULL) { if (oidc_errno == OIDC_EHTTP0) { diff --git a/src/oidc-agent/oidcd/internal_request_handler.c b/src/oidc-agent/oidcd/internal_request_handler.c index fad04b37..4b063163 100644 --- a/src/oidc-agent/oidcd/internal_request_handler.c +++ b/src/oidc-agent/oidcd/internal_request_handler.c @@ -24,3 +24,12 @@ void oidcd_handleUpdateRefreshToken(const struct ipcPipe pipes, "file failed. You may want to revoke the new refresh token or pass it " "to oidc-gen --rt"); } + +void oidcd_handleUpdateIssuer(const struct ipcPipe pipes, + const char* issuer_url, const char* short_name, + const char* action) { + char* res = ipc_communicateThroughPipe(pipes, INT_REQUEST_UPD_ISSUER, + issuer_url, short_name, action); + // update issuer request never returns error, so we can ignore it + secFree(res); +} diff --git a/src/oidc-agent/oidcd/internal_request_handler.h b/src/oidc-agent/oidcd/internal_request_handler.h index 4bba493f..1f44cc41 100644 --- a/src/oidc-agent/oidcd/internal_request_handler.h +++ b/src/oidc-agent/oidcd/internal_request_handler.h @@ -3,7 +3,8 @@ #include "ipc/pipe.h" -void oidcd_handleUpdateRefreshToken(const struct ipcPipe, const char*, - const char*); +void oidcd_handleUpdateRefreshToken(struct ipcPipe, const char*, const char*); +void oidcd_handleUpdateIssuer(struct ipcPipe pipes, const char* issuer_url, + const char* short_name, const char* action); #endif // OIDCD_INTERNAL_REQUEST_HANDLER_H diff --git a/src/oidc-agent/oidcd/oidcd_handler.c b/src/oidc-agent/oidcd/oidcd_handler.c index 24af9641..55ec195d 100644 --- a/src/oidc-agent/oidcd/oidcd_handler.c +++ b/src/oidc-agent/oidcd/oidcd_handler.c @@ -10,14 +10,13 @@ #include "defines/agent_values.h" #include "defines/ipc_values.h" -#include "defines/mytoken_values.h" #include "defines/oidc_values.h" #include "defines/version.h" #include "deviceCodeEntry.h" +#include "internal_request_handler.h" #include "ipc/pipe.h" #include "ipc/serveripc.h" #include "oidc-agent/agent_state.h" -#include "oidc-agent/httpserver/startHttpserver.h" #include "oidc-agent/httpserver/termHttpserver.h" #include "oidc-agent/mytoken/oidc_flow.h" #include "oidc-agent/mytoken/submytoken.h" @@ -34,6 +33,8 @@ #include "oidc-agent/oidcd/parse_internal.h" #include "utils/accountUtils.h" #include "utils/agentLogger.h" +#include "utils/config/agent_config.h" +#include "utils/config/gen_config.h" #include "utils/crypt/crypt.h" #include "utils/crypt/dbCryptUtils.h" #include "utils/db/account_db.h" @@ -79,7 +80,6 @@ void initAuthCodeFlow(struct oidc_account* account, struct ipcPipe pipes, ipc_writeOidcErrnoToPipe(pipes); secFree(code_verifier); secFree(*state_ptr); - secFreeAccount(account); return; } // agent_log(DEBUG, "code_verifier for state '%s' is '%s'", @@ -104,9 +104,9 @@ void _handleGenFlows(struct ipcPipe pipes, struct oidc_account* account, int success = 0; list_t* flows = parseFlow(flow); list_node_t* current_flow; - list_iterator_t* it = list_iterator_new(flows, LIST_HEAD); - unsigned int numberOfFlows = flows->len; - unsigned int flowsTried = 0; + list_iterator_t* it = list_iterator_new(flows, LIST_HEAD); + unsigned int numberOfFlows = flows->len; + unsigned int flowsTried = 0; while ((current_flow = list_iterator_next(it))) { flowsTried++; if (strcaseequal(current_flow->val, FLOW_VALUE_REFRESH)) { @@ -161,7 +161,7 @@ void _handleGenFlows(struct ipcPipe pipes, struct oidc_account* account, ? initMytokenOIDCFlow(account) : initDeviceFlow(account); if (dc == NULL) { - if (flowsTriedval, FLOW_VALUE_CODE) && !hasRedirectUris(account)) { - if (flowsTriedval); } ipc_writeToPipe(pipes, RESPONSE_ERROR, msg); @@ -205,6 +205,8 @@ void _handleGenFlows(struct ipcPipe pipes, struct oidc_account* account, account_setUsername(account, NULL); account_setPassword(account, NULL); if (success && account_refreshTokenIsValid(account) && !only_at) { + oidcd_handleUpdateIssuer(pipes, account_getIssuerUrl(account), + account_getName(account), INT_ACTION_VALUE_ADD); char* json = accountToJSONString(account); ipc_writeToPipe(pipes, RESPONSE_STATUS_CONFIG, STATUS_SUCCESS, json); secFree(json); @@ -234,7 +236,7 @@ void oidcd_handleGen(struct ipcPipe pipes, const char* account_json, ipc_writeOidcErrnoToPipe(pipes); return; } - if (getIssuerConfig(account) != OIDC_SUCCESS) { + if (obtainIssuerConfig(account) != OIDC_SUCCESS) { secFreeAccount(account); ipc_writeOidcErrnoToPipe(pipes); return; @@ -264,7 +266,7 @@ oidc_error_t addAccount(struct ipcPipe pipes, struct oidc_account* account) { oidc_setArgNullFuncError(__func__); return oidc_errno; } - if (getIssuerConfig(account) != OIDC_SUCCESS) { + if (obtainIssuerConfig(account) != OIDC_SUCCESS) { return oidc_errno; } if (!strValid(account_getTokenEndpoint(account))) { @@ -279,6 +281,8 @@ oidc_error_t addAccount(struct ipcPipe pipes, struct oidc_account* account) { return oidc_errno; } db_addAccountEncrypted(account); + oidcd_handleUpdateIssuer(pipes, account_getIssuerUrl(account), + account_getName(account), INT_ACTION_VALUE_ADD); return OIDC_SUCCESS; } @@ -356,7 +360,7 @@ void oidcd_handleDelete(struct ipcPipe pipes, const char* account_json) { ipc_writeOidcErrnoToPipe(pipes); return; } - if (getIssuerConfig(account) != OIDC_SUCCESS) { + if (obtainIssuerConfig(account) != OIDC_SUCCESS) { secFreeAccount(account); ipc_writeOidcErrnoToPipe(pipes); return; @@ -368,6 +372,8 @@ void oidcd_handleDelete(struct ipcPipe pipes, const char* account_json) { secFree(error); return; } + oidcd_handleUpdateIssuer(pipes, account_getIssuerUrl(account), + account_getName(account), INT_ACTION_VALUE_REMOVE); accountDB_removeIfFound(account); secFreeAccount(account); ipc_writeToPipe(pipes, RESPONSE_STATUS_SUCCESS); @@ -534,8 +540,8 @@ struct oidc_account* _getLoadedUnencryptedAccount( } struct oidc_account* _getLoadedUnencryptedAccountForIssuer( - struct ipcPipe pipes, const char* issuer, const char* application_hint, - const struct arguments* arguments) { + struct ipcPipe pipes, const char* issuer, const char* scope, + const char* application_hint, const struct arguments* arguments) { struct oidc_account* account = NULL; list_t* accounts = db_findAccountsByIssuerUrl(issuer); if (accounts == NULL) { // no accounts loaded for this issuer @@ -545,8 +551,19 @@ struct oidc_account* _getLoadedUnencryptedAccountForIssuer( } char* defaultAccount = oidcd_queryDefaultAccountIssuer(pipes, issuer); if (defaultAccount == NULL) { - ipc_writeToPipe(pipes, RESPONSE_ERROR, ACCOUNT_NOT_LOADED); - return NULL; + if (getAgentConfig()->autogen) { + const char* scopes = AGENT_SCOPE_ALL; + switch (getAgentConfig()->autogenscopemode) { + case AGENTCONFIG_AUTOGENSCOPEMODE_EXACT: scopes = scope; + } + + agent_log(DEBUG, "Send autogen request for '%s'", issuer); + ipc_writeToPipe(pipes, INT_REQUEST_AUTOGEN, issuer, scopes, + application_hint ?: ""); + // we abort the current request and oidcp will start a gen + // flow and after success resend the original request + return NULL; + } } oidc_error_t autoload_error = oidcd_autoload(pipes, defaultAccount, issuer, application_hint); @@ -614,7 +631,7 @@ void oidcd_handleTokenIssuer(struct ipcPipe pipes, const char* issuer, time_t min_valid_period = min_valid_period_str != NULL ? strToInt(min_valid_period_str) : 0; struct oidc_account* account = _getLoadedUnencryptedAccountForIssuer( - pipes, issuer, application_hint, arguments); + pipes, issuer, scope, application_hint, arguments); if (account == NULL) { return; } @@ -746,10 +763,11 @@ void oidcd_handleIdToken(struct ipcPipe pipes, const char* short_name, const struct arguments* arguments) { agent_log(DEBUG, "Handle ID-Token request from %s", application_hint); struct oidc_account* account = - short_name != NULL ? _getLoadedUnencryptedAccount( - pipes, short_name, application_hint, arguments) - : _getLoadedUnencryptedAccountForIssuer( - pipes, issuer, application_hint, arguments); + short_name != NULL + ? _getLoadedUnencryptedAccount(pipes, short_name, application_hint, + arguments) + : _getLoadedUnencryptedAccountForIssuer(pipes, issuer, scope, + application_hint, arguments); if (account == NULL) { return; } @@ -793,7 +811,7 @@ void oidcd_handleRegister(struct ipcPipe pipes, const char* account_json, "register a new one."); return; } - if (getIssuerConfig(account) != OIDC_SUCCESS) { + if (obtainIssuerConfig(account) != OIDC_SUCCESS) { secFreeAccount(account); ipc_writeOidcErrnoToPipe(pipes); return; @@ -846,7 +864,6 @@ void oidcd_handleRegister(struct ipcPipe pipes, const char* account_json, secFree(scopes); secFree(res); secFreeAccount(account); - return; } void oidcd_handleCodeExchange(struct ipcPipe pipes, const char* redirected_uri, @@ -894,7 +911,7 @@ void oidcd_handleCodeExchange(struct ipcPipe pipes, const char* redirected_uri, codeVerifierDB_removeIfFound(cee); return; } - if (getIssuerConfig(account) != OIDC_SUCCESS) { + if (obtainIssuerConfig(account) != OIDC_SUCCESS) { ipc_writeOidcErrnoToPipe(pipes); secFreeCodeState(codeState); secFreeCodeExchangeContent(cee); @@ -911,6 +928,10 @@ void oidcd_handleCodeExchange(struct ipcPipe pipes, const char* redirected_uri, return; } if (account_refreshTokenIsValid(account) && !only_at) { + if (fromGen) { + oidcd_handleUpdateIssuer(pipes, account_getIssuerUrl(account), + account_getName(account), INT_ACTION_VALUE_ADD); + } char* json = accountToJSONString(account); ipc_writeToPipe(pipes, RESPONSE_STATUS_CONFIG, STATUS_SUCCESS, json); secFree(json); @@ -967,7 +988,7 @@ void oidcd_handleDeviceLookup(struct ipcPipe pipes, const char* device_json, deviceCodeDB_removeIfFound(dce); return; } - if (getIssuerConfig(account) != OIDC_SUCCESS) { + if (obtainIssuerConfig(account) != OIDC_SUCCESS) { ipc_writeOidcErrnoToPipe(pipes); secFreeDeviceCode(dc); secFreeDeviceCodeEntryContent(dce); @@ -995,6 +1016,8 @@ void oidcd_handleDeviceLookup(struct ipcPipe pipes, const char* device_json, secFreeDeviceCode(dc); const int only_at = strToInt(only_at_str); if (account_refreshTokenIsValid(account) && !only_at) { + oidcd_handleUpdateIssuer(pipes, account_getIssuerUrl(account), + account_getName(account), INT_ACTION_VALUE_ADD); char* json = accountToJSONString(account); ipc_writeToPipe(pipes, RESPONSE_STATUS_CONFIG, STATUS_SUCCESS, json); secFree(json); @@ -1034,7 +1057,7 @@ void oidcd_handleStateLookUp(struct ipcPipe pipes, char* state) { } if (account->usedStateChecked) { ipc_writeToPipe(pipes, RESPONSE_STATUS_INFO, STATUS_FOUNDBUTDONE, - "Account config already retrieved from another oidc-gen"); + "Account config already retrieved"); db_addAccountEncrypted(account); // reencrypting return; } @@ -1046,6 +1069,8 @@ void oidcd_handleStateLookUp(struct ipcPipe pipes, char* state) { account_getTokenExpiresAt(account)); accountDB_removeIfFound(account); } else { + oidcd_handleUpdateIssuer(pipes, account_getIssuerUrl(account), + account_getName(account), INT_ACTION_VALUE_ADD); char* config = accountToJSONString(account); ipc_writeToPipe(pipes, RESPONSE_STATUS_CONFIG, STATUS_SUCCESS, config); secFree(config); @@ -1114,8 +1139,9 @@ void oidcd_handleMytokenProvidersLookup(struct ipcPipe pipes, } list_t* _getNameListLoadedAccounts() { - list_t* accounts = accountDB_getList(); - list_t* names = list_new(); + list_t* accounts = accountDB_getList(); + list_t* names = list_new(); + names->match = (matchFunction)strequal; list_node_t* node; list_iterator_t* it = list_iterator_new(accounts, LIST_HEAD); while ((node = list_iterator_next(it))) { @@ -1141,7 +1167,6 @@ char* _argumentsToOptionsText(const struct arguments* arguments) { "Auto Re-authenticate:\t%s\n" "Use custom URI scheme:\t%s\n" "Webserver:\t\t%s\n" - "Store password:\t\t%s\n" "Allow ID-Token:\t\t%s\n" "Group:\t\t\t%s\n" "Daemon:\t\t\t%s\n" @@ -1150,50 +1175,30 @@ char* _argumentsToOptionsText(const struct arguments* arguments) { char* lifetime = arguments->lifetime ? oidc_sprintf("%lu seconds", arguments->lifetime) : oidc_strcopy("Forever"); - char* pw_lifetime = - arguments->pw_lifetime.argProvided - ? arguments->pw_lifetime.lifetime - ? oidc_sprintf("%lu seconds", arguments->pw_lifetime.lifetime) - : oidc_strcopy("Forever") - : NULL; - char* store_pw = arguments->pw_lifetime.argProvided - ? oidc_sprintf("true - %s", pw_lifetime) - : oidc_strcopy("false"); - secFree(pw_lifetime); - char* options = + char* options = oidc_sprintf(fmt, lifetime, arguments->confirm ? "true" : "false", arguments->no_autoload ? "false" : "true", arguments->no_autoreauthenticate ? "false" : "true", arguments->no_scheme ? "false" : "true", - arguments->no_webserver ? "false" : "true", store_pw, + arguments->no_webserver ? "false" : "true", arguments->always_allow_idtoken ? "true" : "false", arguments->group ? arguments->group : "false", arguments->console ? "false" : "true", arguments->debug ? "true" : "false", arguments->log_console ? "true" : "false"); secFree(lifetime); - secFree(store_pw); return options; } char* _argumentsToCommandLineOptions(const struct arguments* arguments) { list_t* options = list_new(); options->match = (matchFunction)strequal; - options->free = (void(*)(void*))_secFree; + options->free = (void (*)(void*))_secFree; if (arguments->lifetime) { list_rpush(options, list_node_new(oidc_sprintf("--lifetime=%ld", arguments->lifetime))); } - if (arguments->pw_lifetime.argProvided) { - if (arguments->pw_lifetime.lifetime) { - list_rpush(options, - list_node_new(oidc_sprintf("--pw-store=%ld", - arguments->pw_lifetime.lifetime))); - } else { - list_rpush(options, list_node_new(oidc_strcopy("--pw-store"))); - } - } if (arguments->confirm) { list_rpush(options, list_node_new(oidc_strcopy("--confirm"))); } @@ -1241,9 +1246,9 @@ void oidcd_handleAgentStatus(struct ipcPipe pipes, "####################################\n" "\nThis agent is running version %s.\n\nThis agent was started with the " "following options:\n%s\nCurrently there are %d accounts loaded: %s\n\n"; - list_t* names = _getNameListLoadedAccounts(); - int num_loaded = 0; - char* names_str = NULL; + list_t* names = _getNameListLoadedAccounts(); + unsigned int num_loaded = 0; + char* names_str = NULL; if (names != NULL) { num_loaded = names->len; names_str = listToDelimitedString(names, ", "); @@ -1261,7 +1266,7 @@ void oidcd_handleAgentStatus(struct ipcPipe pipes, void oidcd_handleAgentStatusJSON(struct ipcPipe pipes, const struct arguments* arguments) { list_t* names = _getNameListLoadedAccounts(); - cJSON* names_j = listToJSONArray(names); + cJSON* names_j = stringListToJSONArray(names); secFreeList(names); char* options = _argumentsToCommandLineOptions(arguments); cJSON* json = diff --git a/src/oidc-agent/oidcd/parse_internal.c b/src/oidc-agent/oidcd/parse_internal.c index f88ce899..202fa37c 100644 --- a/src/oidc-agent/oidcd/parse_internal.c +++ b/src/oidc-agent/oidcd/parse_internal.c @@ -1,7 +1,9 @@ #include "parse_internal.h" #include "defines/ipc_values.h" +#include "ipc/pipe.h" #include "utils/agentLogger.h" +#include "utils/config/issuerConfig.h" #include "utils/json.h" #include "utils/memory.h" #include "utils/oidc_error.h" @@ -65,16 +67,18 @@ oidc_error_t parseForErrorCode(char* res) { return OIDC_SUCCESS; } -char* parseStateLookupRes(char* res) { - INIT_KEY_VALUE(IPC_KEY_STATUS, IPC_KEY_CONFIG, OIDC_KEY_ERROR); +char* parseStateLookupRes(char* res, struct ipcPipe pipes) { + INIT_KEY_VALUE(IPC_KEY_STATUS, IPC_KEY_CONFIG, OIDC_KEY_ERROR, + IPC_KEY_REQUEST, INT_IPC_KEY_ACTION, IPC_KEY_ISSUERURL, + IPC_KEY_SHORTNAME); if (CALL_GETJSONVALUES(res) < 0) { secFree(res); return NULL; } - KEY_VALUE_VARS(status, config, error); + KEY_VALUE_VARS(status, config, error, request, action, issuer, shortname); secFree(res); if (_error != NULL) { - agent_log(ERROR, _error); + agent_log(ERROR, "%s", _error); SEC_FREE_KEY_VALUES(); return NULL; } @@ -88,6 +92,12 @@ char* parseStateLookupRes(char* res) { oidc_errno = OIDC_EWRONGSTATE; return NULL; } + if (strcaseequal(_request, INT_REQUEST_VALUE_UPD_ISSUER)) { + oidcp_updateIssuerConfig(_action, _issuer, _shortname); + SEC_FREE_KEY_VALUES(); + return parseStateLookupRes( + ipc_communicateThroughPipe(pipes, RESPONSE_SUCCESS), pipes); + } SEC_FREE_KEY_VALUES(); return NULL; } diff --git a/src/oidc-agent/oidcd/parse_internal.h b/src/oidc-agent/oidcd/parse_internal.h index 72426f4e..d90b4311 100644 --- a/src/oidc-agent/oidcd/parse_internal.h +++ b/src/oidc-agent/oidcd/parse_internal.h @@ -1,11 +1,12 @@ #ifndef OIDCAGENT_INTERNAL_PARSER_H #define OIDCAGENT_INTERNAL_PARSER_H +#include "ipc/pipe.h" #include "utils/oidc_error.h" char* parseForConfig(char* res); char* parseForInfo(char* res); oidc_error_t parseForErrorCode(char* res); -char* parseStateLookupRes(char* res); +char* parseStateLookupRes(char* res, struct ipcPipe pipes); #endif // OIDCAGENT_INTERNAL_PARSER_H diff --git a/src/oidc-agent/oidcp/config_updater.c b/src/oidc-agent/oidcp/config_updater.c index d2905791..0dee354c 100644 --- a/src/oidc-agent/oidcp/config_updater.c +++ b/src/oidc-agent/oidcp/config_updater.c @@ -4,10 +4,14 @@ #include "defines/settings.h" #include "oidc-agent/oidcp/passwords/password_store.h" #include "proxy_handler.h" +#include "utils/config/gen_config.h" #include "utils/crypt/cryptUtils.h" #include "utils/crypt/gpg/gpg.h" #include "utils/file_io/cryptFileUtils.h" +#include "utils/file_io/oidc_file_io.h" #include "utils/json.h" +#include "utils/prompting/promptUtils.h" +#include "utils/string/stringUtils.h" oidc_error_t _updateRT(char* file_content, const char* shortname, const char* refresh_token, const char* password, @@ -64,21 +68,36 @@ oidc_error_t writeOIDCFile(const char* content, const char* shortname) { } char* gpg_key = getGPGKeyFor(shortname) ?: extractPGPKeyIDFromOIDCFile(shortname); - char* password = NULL; - char* file_content = NULL; + if (gpg_key == NULL && !oidcFileDoesExist(shortname) && + getGenConfig()->default_gpg_key) { + gpg_key = oidc_strcopy(getGenConfig()->default_gpg_key); + } + char* password = NULL; if (gpg_key == NULL) { - for (int i = 0; i < MAX_PASS_TRIES && file_content == NULL; i++) { - password = getPasswordFor(shortname); - if (password == NULL) { - oidc_errno = OIDC_EUSRPWCNCL; + if (!oidcFileDoesExist(shortname)) { + password = getEncryptionPasswordForAccountConfig(shortname, NULL, NULL, + NULL, NULL); + } else { + char* file_content = NULL; + for (int i = 0; i < MAX_PASS_TRIES && file_content == NULL; i++) { + secFree(password); + password = getPasswordFor(shortname); + if (password == NULL) { + oidc_errno = OIDC_EUSRPWCNCL; + return oidc_errno; + } + file_content = decryptOidcFile(shortname, password); + } + if (file_content == NULL) { + secFree(password); return oidc_errno; } - file_content = decryptOidcFile(shortname, password); + secFree(file_content); } } - if (file_content == NULL) { - return oidc_errno; - } - secFree(file_content); - return encryptAndWriteToOidcFile(content, shortname, password, gpg_key); + oidc_error_t ret = + encryptAndWriteToOidcFile(content, shortname, password, gpg_key); + secFree(gpg_key); + secFree(password); + return ret; } \ No newline at end of file diff --git a/src/oidc-agent/oidcp/oidcp.c b/src/oidc-agent/oidcp/oidcp.c index cfddd14f..0a6d4541 100644 --- a/src/oidc-agent/oidcp/oidcp.c +++ b/src/oidc-agent/oidcp/oidcp.c @@ -1,13 +1,17 @@ #define _XOPEN_SOURCE 500 +#define _POSIX_C_SOURCE 200112L #include "oidcp.h" #include #include +#include #include #include #include +#include "account/account.h" +#include "account/issuer_helper.h" #include "config_updater.h" #include "defines/ipc_values.h" #include "defines/oidc_values.h" @@ -25,7 +29,11 @@ #include "oidc-agent/oidcp/passwords/password_store.h" #include "oidc-agent/oidcp/proxy_handler.h" #include "oidc-agent/oidcp/start_oidcd.h" +#include "oidc-agent/stats/statlogger.h" +#include "oidc-gen/promptAndSet/name.h" #include "utils/agentLogger.h" +#include "utils/config/gen_config.h" +#include "utils/config/issuerConfig.h" #include "utils/crypt/crypt.h" #include "utils/db/connection_db.h" #include "utils/disableTracing.h" @@ -35,13 +43,100 @@ #include "utils/oidc/device.h" #include "utils/printer.h" #include "utils/printerUtils.h" +#include "utils/prompting/getprompt.h" +#include "utils/prompting/prompt_mode.h" #include "utils/string/stringUtils.h" #include "utils/uriUtils.h" #ifdef __MSYS__ #include "utils/registryConnector.h" #endif -struct connection* unix_listencon; +static void handleAccountInfo(struct ipcPipe pipes, int sock) { + char* loaded_res = ipc_communicateThroughPipe(pipes, REQUEST_LOADEDACCOUNTS); + char* info = parseForInfo(loaded_res); + list_t* loaded = JSONArrayStringToList(info); + secFree(info); + char* accountsInfo = getAccountInfos(loaded); + secFreeList(loaded); + server_ipc_write(sock, RESPONSE_SUCCESS_INFO_OBJECT, accountsInfo); + secFree(accountsInfo); +} + +static struct connection* unix_listencon; + +static pid_t parent_pid = -1; + +static void check_parent_alive(void) { + if (parent_pid != -1 && getppid() != parent_pid) { + exit(EXIT_SUCCESS); + } +} + +_Noreturn static void handleClientComm(struct ipcPipe pipes, + const struct arguments* arguments, + time_t parent_alive_interval) { + connectionDB_new(); + connectionDB_setFreeFunction((void (*)(void*)) & _secFreeConnection); + connectionDB_setMatchFunction((matchFunction)connection_comparator); + + time_t deadline = 0; + while (1) { + deadline = getMinPasswordDeath(); + if (parent_alive_interval > 0) { + time_t parent_check = time(NULL) + parent_alive_interval; + if (deadline == 0 || deadline > parent_check) { + deadline = parent_check; + } + } + struct connection* con = ipc_readAsyncFromMultipleConnectionsWithTimeout( + *unix_listencon, deadline); + if (con == NULL) { // timeout reached + if (parent_alive_interval != 0) { + check_parent_alive(); + } + removeDeathPasswords(); + continue; + } + char* client_req = server_ipc_read(*(con->msgsock)); + if (client_req == NULL) { + server_ipc_writeOidcErrnoPlain(*(con->msgsock)); + } else { + statlog(client_req); + INIT_KEY_VALUE(IPC_KEY_REQUEST, IPC_KEY_PASSWORDENTRY, IPC_KEY_SHORTNAME); + if (CALL_GETJSONVALUES(client_req) < 0) { + server_ipc_write(*(con->msgsock), RESPONSE_BADREQUEST, oidc_serror()); + } else { + KEY_VALUE_VARS(request, passwordentry, shortname); + if (_request) { + unsigned char skipOIDCDComm = 0; + if (strequal(_request, REQUEST_VALUE_ADD) || + strequal(_request, REQUEST_VALUE_GEN)) { + pw_handleSave(_passwordentry); + } else if (strequal(_request, REQUEST_VALUE_REMOVE)) { + removePasswordFor(_shortname); + } else if (strequal(_request, REQUEST_VALUE_REMOVEALL)) { + removeAllPasswords(); + } else if (strequal(_request, REQUEST_VALUE_ACCOUNTINFO)) { + handleAccountInfo(pipes, *(con->msgsock)); + skipOIDCDComm = 1; + } + if (!skipOIDCDComm) { + handleOidcdComm(pipes, *(con->msgsock), client_req, arguments); + } + } else { // no request type + server_ipc_write(*(con->msgsock), RESPONSE_BADREQUEST, + "No request type."); + } + } + SEC_FREE_KEY_VALUES(); + secFree(client_req); + } + agent_log(DEBUG, "Remove con from pool"); + connectionDB_removeIfFound(con); + agent_log(DEBUG, "Currently there are %lu connections", + connectionDB_getSize()); + } +} int main(int argc, char** argv) { platform_disable_tracing(); @@ -118,14 +213,35 @@ int main(int argc, char** argv) { signal(SIGPIPE, SIG_IGN); if (ipc_server_init(unix_listencon, arguments.group, arguments.socket_path) != OIDC_SUCCESS) { - printError("%s\n", oidc_serror()); + oidc_perror(); exit(EXIT_FAILURE); } + time_t parent_alive_interval = 0; if (!arguments.console) { - pid_t daemon_pid = daemonize(); + unsigned char commandSet = strValid(arguments.command); + if (commandSet) { + parent_alive_interval = 10; + } + pid_t daemon_pid = daemonize(!commandSet); if (daemon_pid > 0) { -// Export PID of new daemon + // Export PID of new daemon + if (commandSet) { + char* pid_str = oidc_sprintf("%d", daemon_pid); + if (setenv(OIDC_SOCK_ENV_NAME, unix_listencon->server->sun_path, 1) == + -1 || + setenv(OIDC_PID_ENV_NAME, pid_str, 1) == -1) { + secFree(pid_str); + oidc_setErrnoError(); + oidc_perror(); + exit(oidc_errno); + } + secFree(pid_str); + execvp(arguments.command, arguments.args); + oidc_setErrnoError(); + oidc_perror(); + exit(oidc_errno); + } #ifdef __MSYS__ char daemon_pid_string[12]; sprintf(daemon_pid_string, "%d", daemon_pid); @@ -158,63 +274,17 @@ int main(int argc, char** argv) { #endif } + parent_pid = getppid(); + agent_state.defaultTimeout = arguments.lifetime; struct ipcPipe pipes = startOidcd(&arguments); - if (ipc_bindAndListen(unix_listencon) != 0) { + if (ipc_bindAndListen(unix_listencon, arguments.group) != 0) { exit(EXIT_FAILURE); } - handleClientComm(pipes, &arguments); -} - -_Noreturn void handleClientComm(struct ipcPipe pipes, - const struct arguments* arguments) { - connectionDB_new(); - connectionDB_setFreeFunction((void (*)(void*)) & _secFreeConnection); - connectionDB_setMatchFunction((matchFunction)connection_comparator); - - time_t minDeath = 0; - while (1) { - minDeath = getMinPasswordDeath(); - struct connection* con = ipc_readAsyncFromMultipleConnectionsWithTimeout( - *unix_listencon, minDeath); - if (con == NULL) { // timeout reached - removeDeathPasswords(); - continue; - } - char* client_req = server_ipc_read(*(con->msgsock)); - if (client_req == NULL) { - server_ipc_writeOidcErrnoPlain(*(con->msgsock)); - } else { // NULL != q - INIT_KEY_VALUE(IPC_KEY_REQUEST, IPC_KEY_PASSWORDENTRY, IPC_KEY_SHORTNAME); - if (CALL_GETJSONVALUES(client_req) < 0) { - server_ipc_write(*(con->msgsock), RESPONSE_BADREQUEST, oidc_serror()); - } else { - KEY_VALUE_VARS(request, passwordentry, shortname); - if (_request) { - if (strequal(_request, REQUEST_VALUE_ADD) || - strequal(_request, REQUEST_VALUE_GEN)) { - pw_handleSave(_passwordentry, arguments->pw_lifetime); - } else if (strequal(_request, REQUEST_VALUE_REMOVE)) { - removePasswordFor(_shortname); - } else if (strequal(_request, REQUEST_VALUE_REMOVEALL)) { - removeAllPasswords(); - } - handleOidcdComm(pipes, *(con->msgsock), client_req, arguments); - } else { // no request type - server_ipc_write(*(con->msgsock), RESPONSE_BADREQUEST, - "No request type."); - } - } - SEC_FREE_KEY_VALUES(); - secFree(client_req); - } - agent_log(DEBUG, "Remove con from pool"); - connectionDB_removeIfFound(con); - agent_log(DEBUG, "Currently there are %lu connections", - connectionDB_getSize()); - } + set_prompt_mode(PROMPT_MODE_GUI); + handleClientComm(pipes, &arguments, parent_alive_interval); } char* _extractShortnameFromReauthenticateInfo(const char* info) { @@ -274,9 +344,12 @@ int _waitForCodeExchangeRequest(time_t expiration, const char* expected_state, if (!strcaseequal(_request, REQUEST_VALUE_CODEEXCHANGE)) { secFree(client_req); SEC_FREE_KEY_VALUES(); - server_ipc_write( - *(con->msgsock), RESPONSE_ERROR, - "request currently not acceptable; please try again later"); + time_t remaining_time = expiration - time(NULL); + char* error_msg = oidc_sprintf( + "request currently not acceptable; please try again later (%ds)", + remaining_time); + server_ipc_write(*(con->msgsock), RESPONSE_ERROR, error_msg); + secFree(error_msg); connectionDB_removeIfFound(con); agent_log(DEBUG, "Currently there are %lu connections", connectionDB_getSize()); @@ -307,7 +380,7 @@ int _waitForCodeExchangeRequest(time_t expiration, const char* expected_state, connectionDB_removeIfFound(con); agent_log(DEBUG, "Currently there are %lu connections", connectionDB_getSize()); - agent_log(DEBUG, "Reaturning"); + agent_log(DEBUG, "Returning"); return 0; } secFree(state); @@ -319,34 +392,63 @@ int _waitForCodeExchangeRequest(time_t expiration, const char* expected_state, } } -void doReauthenticate(struct ipcPipe pipes, int sock, - const char* original_client_req, const char* oidcd_res, - const char* info) { - logger(DEBUG, "Doing automatic reauthentication"); - char* shortname = _extractShortnameFromReauthenticateInfo(info); - if (shortname == NULL) { - server_ipc_write(sock, "%s", - oidcd_res); // Forward oidcd response to client - return; +const char* _getMytokenURLToUse(struct ipcPipe pipes, const char* issuer) { + unsigned char useMytokenServer = 0; + if (!getGenConfig()->prefer_mytoken_over_oidc || + getGenConfig()->default_mytoken_server == NULL) { + return NULL; } - logger(DEBUG, "Extracted shortname '%s'", shortname); - char* reauth_res = - ipc_communicateThroughPipe(pipes, REQUEST_REAUTHENTICATE, shortname); - SHUTDOWN_IF_D_DIED(reauth_res); - INIT_KEY_VALUE(IPC_KEY_DEVICE, IPC_KEY_URI, OIDC_KEY_STATE); - if (CALL_GETJSONVALUES(reauth_res) < 0) { - server_ipc_write(sock, "%s", oidcd_res); - secFree(reauth_res); + char* providers_res = ipc_communicateThroughPipe( + pipes, REQUEST_MYTOKEN_PROVIDERS, getGenConfig()->default_mytoken_server, + "", ""); + if (providers_res == NULL) { + return NULL; + } + char* providers = getJSONValueFromString(providers_res, IPC_KEY_INFO); + secFree(providers_res); + if (providers == NULL) { + return NULL; + } + list_t* providers_l = JSONArrayStringToList(providers); + secFree(providers); + if (providers_l == NULL) { + return NULL; + } + list_node_t* node; + list_iterator_t* it = list_iterator_new(providers_l, LIST_HEAD); + while ((node = list_iterator_next(it))) { + char* p = node->val; + char* iss = getJSONValueFromString(p, OIDC_KEY_ISSUER); + if (compIssuerUrls(issuer, iss)) { + useMytokenServer = 1; + break; + } + } + list_iterator_destroy(it); + secFreeList(providers_l); + return useMytokenServer ? getGenConfig()->default_mytoken_server : NULL; +} + +void _parseInternalGen(struct ipcPipe pipes, int sock, char* res, + const char* original_client_req, + const char* error_res_fmt, const char* error_res_arg, + const char* shortname, unsigned char reauth_intro) { + SHUTDOWN_IF_D_DIED(res); + INIT_KEY_VALUE(IPC_KEY_DEVICE, IPC_KEY_URI, OIDC_KEY_STATE, IPC_KEY_REQUEST, + INT_IPC_KEY_ACTION, IPC_KEY_ISSUERURL, IPC_KEY_SHORTNAME); + if (CALL_GETJSONVALUES(res) < 0) { + server_ipc_write(sock, error_res_fmt, error_res_arg); + secFree(res); SEC_FREE_KEY_VALUES(); return; } - secFree(reauth_res); - KEY_VALUE_VARS(device, url, state); + secFree(res); + KEY_VALUE_VARS(device, url, state, request, action, issuer, shortname); if (_url) { - agent_displayAuthCodeURL(_url, shortname); + agent_displayAuthCodeURL(_url, shortname, reauth_intro); time_t timeout = time(NULL) + AGENT_PROMPT_TIMEOUT; if (_waitForCodeExchangeRequest(timeout, _state, pipes)) { - server_ipc_write(sock, "%s", oidcd_res); + server_ipc_write(sock, error_res_fmt, error_res_arg); SEC_FREE_KEY_VALUES(); return; } @@ -354,21 +456,18 @@ void doReauthenticate(struct ipcPipe pipes, int sock, char* lookup_res = ipc_communicateThroughPipe(pipes, REQUEST_STATELOOKUP, _state); SHUTDOWN_IF_D_DIED(lookup_res); - char* config = parseStateLookupRes(lookup_res); + char* config = parseStateLookupRes(lookup_res, pipes); if (config == NULL) { - server_ipc_write(sock, "%s", oidcd_res); + server_ipc_write(sock, error_res_fmt, error_res_arg); SEC_FREE_KEY_VALUES(); - secFree(shortname); return; } SEC_FREE_KEY_VALUES(); if (writeOIDCFile(config, shortname) != OIDC_SUCCESS) { - server_ipc_write(sock, "%s", oidcd_res); + server_ipc_write(sock, error_res_fmt, error_res_arg); secFree(config); - secFree(shortname); return; } - secFree(shortname); secFree(config); char* final_res = @@ -381,11 +480,10 @@ void doReauthenticate(struct ipcPipe pipes, int sock, struct oidc_device_code* dc = getDeviceCodeFromJSON(_device); if (dc == NULL) { SEC_FREE_KEY_VALUES(); - server_ipc_write(sock, "%s", oidcd_res); - secFree(shortname); + server_ipc_write(sock, error_res_fmt, error_res_arg); return; } - agent_displayDeviceCode(dc, shortname); + agent_displayDeviceCode(dc, shortname, reauth_intro); if (dc->expires_in > AGENT_PROMPT_TIMEOUT) { dc->expires_in = AGENT_PROMPT_TIMEOUT; } @@ -394,18 +492,15 @@ void doReauthenticate(struct ipcPipe pipes, int sock, 0, &pipes); SEC_FREE_KEY_VALUES(); if (config == NULL) { - server_ipc_write(sock, "%s", oidcd_res); - secFree(shortname); + server_ipc_write(sock, error_res_fmt, error_res_arg); return; } if (writeOIDCFile(config, shortname) != OIDC_SUCCESS) { - server_ipc_write(sock, "%s", oidcd_res); secFree(config); - secFree(shortname); + server_ipc_write(sock, error_res_fmt, error_res_arg); return; } secFree(config); - secFree(shortname); char* final_res = ipc_communicateThroughPipe(pipes, "%s", original_client_req); @@ -413,10 +508,126 @@ void doReauthenticate(struct ipcPipe pipes, int sock, secFree(final_res); return; } + if (strcaseequal(_request, INT_REQUEST_VALUE_UPD_ISSUER)) { + oidcp_updateIssuerConfig(_action, _issuer, _shortname); + SEC_FREE_KEY_VALUES(); + return _parseInternalGen( + pipes, sock, ipc_communicateThroughPipe(pipes, RESPONSE_SUCCESS), + original_client_req, error_res_fmt, error_res_arg, shortname, + reauth_intro); + } SEC_FREE_KEY_VALUES(); - server_ipc_write(sock, "%s", oidcd_res); + server_ipc_write(sock, error_res_fmt, error_res_arg); +} + +void handleAutoGen(struct ipcPipe pipes, int sock, + const char* original_client_req, const char* issuer, + const char* scopes, const char* application_hint) { + struct oidc_account* account = secAlloc(sizeof(struct oidc_account)); + set_prompt_mode(PROMPT_MODE_GUI); + set_pw_prompt_mode(PROMPT_MODE_GUI); + account_setIssuerUrl(account, oidc_strcopy(issuer)); + account_setMytokenUrl(account, + oidc_strcopy(_getMytokenURLToUse(pipes, issuer))); + const list_t* flows = NULL; + unsigned char usedUserClient = 0; + if (account_getMytokenUrl(account)) { + if (getGenConfig()->default_mytoken_profile) { + account_setUsedMytokenProfile( + account, + oidc_sprintf("\"%s\"", getGenConfig()->default_mytoken_profile)); + } + } else { + const struct issuerConfig* issConfig = getIssuerConfig(issuer); + if (issConfig && issConfig->user_client && + issConfig->user_client->client_id) { + usedUserClient = 1; + updateAccountWithUserClientInfo(account); + flows = getUserClientFlows(account_getIssuerUrl(account)); + } else { + updateAccountWithPublicClientInfo(account); + flows = getPubClientFlows(account_getIssuerUrl(account)); + } + if (!strValid(account_getClientId(account))) { + secFreeAccount(account); + server_ipc_write(sock, RESPONSE_ERROR, ACCOUNT_NOT_LOADED); + return; + } + } + if (strequal(scopes, AGENT_SCOPE_ALL)) { + if (account_getMytokenUrl(account)) { + account_setScopeExact(account, oidc_strcopy(scopes)); + } else { + account_setScope(account, usedUserClient + ? getScopesForUserClient(account) + : getScopesForPublicClient(account)); + } + } else { + account_setScope(account, oidc_strcopy(scopes)); + } + + agent_log(DEBUG, "Prompting user for confirmation for autogen for '%s'", + issuer); + cJSON* data = generateJSONObject("issuer", cJSON_String, issuer, NULL); + data = jsonAddStringValue(data, "application-hint", application_hint); + char* prompt_text = getprompt(PROMPTTEMPLATE(LINK_IDENTITY), data); + secFreeJson(data); + if (!agent_promptConsentDefaultYes(prompt_text)) { + secFree(prompt_text); + agent_log(DEBUG, "User declined autogen"); + server_ipc_write(sock, RESPONSE_ERROR, ACCOUNT_NOT_LOADED); + return; + } + secFree(prompt_text); + + char* name_suggestion = getTopHost(issuer); + signal(SIGINT, SIG_IGN); + askOrNeedName(account, NULL, NULL, 0, 1, name_suggestion); + signal(SIGINT, SIG_DFL); + secFree(name_suggestion); + if (account_getName(account) == NULL) { // user canceled prompt + secFreeAccount(account); + server_ipc_write(sock, RESPONSE_ERROR, ACCOUNT_NOT_LOADED); + return; + } + char* shortname = oidc_strcopy(account_getName(account)); + + char* flow = listToJSONArrayString((list_t*)flows); + if (flow == NULL) { + flow = oidc_strcopy(account_getMytokenUrl(account) + ? "[\"" FLOW_VALUE_MT_OIDC "\"]" + : "[\"" FLOW_VALUE_DEVICE "\",\"" FLOW_VALUE_CODE + "\"]"); + } + char* account_json = accountToJSONString(account); + secFreeAccount(account); + agent_log(DEBUG, "sending gen request to oidcd"); + char* gen_res = ipc_communicateThroughPipe(pipes, REQUEST_GEN, account_json, + flow, "{}", 0, 0, 0); + secFree(account_json); + secFree(flow); + agent_log(DEBUG, "parsing response of gen"); + _parseInternalGen(pipes, sock, gen_res, original_client_req, RESPONSE_ERROR, + ACCOUNT_NOT_LOADED, shortname, 0); + secFree(shortname); +} + +void doReauthenticate(struct ipcPipe pipes, int sock, + const char* original_client_req, const char* oidcd_res, + const char* info) { + logger(DEBUG, "Doing automatic reauthentication"); + char* shortname = _extractShortnameFromReauthenticateInfo(info); + if (shortname == NULL) { + server_ipc_write(sock, "%s", + oidcd_res); // Forward oidcd response to client + return; + } + logger(DEBUG, "Extracted shortname '%s'", shortname); + char* reauth_res = + ipc_communicateThroughPipe(pipes, REQUEST_REAUTHENTICATE, shortname); + _parseInternalGen(pipes, sock, reauth_res, original_client_req, "%s", + oidcd_res, shortname, 1); secFree(shortname); - return; } void handleOidcdComm(struct ipcPipe pipes, int sock, const char* msg, @@ -424,7 +635,7 @@ void handleOidcdComm(struct ipcPipe pipes, int sock, const char* msg, char* send = oidc_strcopy(msg); INIT_KEY_VALUE(IPC_KEY_REQUEST, OIDC_KEY_REFRESHTOKEN, IPC_KEY_SHORTNAME, IPC_KEY_APPLICATIONHINT, IPC_KEY_ISSUERURL, OIDC_KEY_ERROR, - IPC_KEY_INFO); + IPC_KEY_INFO, INT_IPC_KEY_ACTION, OIDC_KEY_SCOPE); while (1) { // RESET_KEY_VALUE_VALUES_TO_NULL(); char* oidcd_res = ipc_communicateThroughPipe(pipes, "%s", send); @@ -438,7 +649,7 @@ void handleOidcdComm(struct ipcPipe pipes, int sock, const char* msg, return; } KEY_VALUE_VARS(request, refresh_token, shortname, application_hint, issuer, - error, info); + error, info, action, scope); if (_request == NULL) { // if the response is the final response, forward // it to the client if (_error != NULL && _info != NULL && @@ -458,6 +669,7 @@ void handleOidcdComm(struct ipcPipe pipes, int sock, const char* msg, SEC_FREE_KEY_VALUES(); return; } + statlog(oidcd_res); secFree(oidcd_res); if (strequal(_request, INT_REQUEST_VALUE_UPD_REFRESH)) { oidc_error_t e = updateRefreshToken(_shortname, _refresh_token); @@ -465,6 +677,11 @@ void handleOidcdComm(struct ipcPipe pipes, int sock, const char* msg, : oidc_sprintf(RESPONSE_ERROR, oidc_serror()); SEC_FREE_KEY_VALUES(); continue; + } else if (strequal(_request, INT_REQUEST_VALUE_UPD_ISSUER)) { + oidcp_updateIssuerConfig(_action, _issuer, _shortname); + send = oidc_strcopy(RESPONSE_SUCCESS); + SEC_FREE_KEY_VALUES(); + continue; } else if (strequal(_request, INT_REQUEST_VALUE_AUTOLOAD)) { char* config = getAutoloadConfig(_shortname, _issuer, _application_hint); send = config @@ -473,6 +690,10 @@ void handleOidcdComm(struct ipcPipe pipes, int sock, const char* msg, secFree(config); SEC_FREE_KEY_VALUES(); continue; + } else if (strequal(_request, INT_REQUEST_VALUE_AUTOGEN)) { + handleAutoGen(pipes, sock, msg, _issuer, _scope, _application_hint); + SEC_FREE_KEY_VALUES(); + return; } else if (strequal(_request, INT_REQUEST_VALUE_CONFIRM)) { oidc_error_t e = _issuer ? askpass_getConfirmationWithIssuer(_issuer, _shortname, @@ -499,14 +720,13 @@ void handleOidcdComm(struct ipcPipe pipes, int sock, const char* msg, SEC_FREE_KEY_VALUES(); continue; } else if (strequal(_request, INT_REQUEST_VALUE_QUERY_ACCDEFAULT)) { - char* account = NULL; + const char* account = NULL; if (strValid(_issuer)) { // default for this issuer account = getDefaultAccountConfigForIssuer(_issuer); } else { // global default oidc_errno = OIDC_NOTIMPL; // TODO } send = oidc_sprintf(INT_RESPONSE_ACCDEFAULT, account ?: ""); - secFree(account); SEC_FREE_KEY_VALUES(); continue; } else { diff --git a/src/oidc-agent/oidcp/oidcp.h b/src/oidc-agent/oidcp/oidcp.h index 0dd0e010..2c642520 100644 --- a/src/oidc-agent/oidcp/oidcp.h +++ b/src/oidc-agent/oidcp/oidcp.h @@ -10,9 +10,7 @@ const char* argp_program_version = AGENT_VERSION; const char* argp_program_bug_address = BUG_ADDRESS; -void handleOidcdComm(struct ipcPipe pipes, int sock, const char* msg, - const struct arguments* argument); -_Noreturn void handleClientComm(struct ipcPipe pipes, - const struct arguments* arguments); +void handleOidcdComm(struct ipcPipe pipes, int sock, const char* msg, + const struct arguments* argument); #endif // OIDC_PROXY_DAEMON_H diff --git a/src/oidc-agent/oidcp/passwords/agent_prompt.c b/src/oidc-agent/oidcp/passwords/agent_prompt.c index 7d3af18c..de1eefbb 100644 --- a/src/oidc-agent/oidcp/passwords/agent_prompt.c +++ b/src/oidc-agent/oidcp/passwords/agent_prompt.c @@ -5,7 +5,9 @@ #include "oidc-agent/oidc/device_code.h" #include "oidc-gen/qr.h" -#include "utils/prompt.h" +#include "utils/json.h" +#include "utils/prompting/getprompt.h" +#include "utils/prompting/prompt.h" #include "utils/string/stringUtils.h" char* agent_promptPassword(const char* text, const char* label, @@ -37,17 +39,8 @@ static const char* const intro_fmt = "re-authenticate.\n"; void agent_displayDeviceCode(const struct oidc_device_code* device, - const char* shortname) { - char* intro = oidc_sprintf(intro_fmt, shortname); - char* code_part = oidc_device_getUserCode(*device) - ? oidc_sprintf(" and enter the following code:\n\n%s", - oidc_device_getUserCode(*device)) - : oidc_strcopy(""); - char* text = oidc_sprintf( - "%sTo continue please open the following URL in a browser on any device " - "(or use the QR code)%s\n", - intro, code_part); - secFree(code_part); + const char* shortname, + unsigned char reauth_intro) { const char* qr = "/tmp/oidc-qr"; const char* url = strValid(oidc_device_getVerificationUriComplete(*device)) ? oidc_device_getVerificationUriComplete(*device) @@ -55,16 +48,31 @@ void agent_displayDeviceCode(const struct oidc_device_code* device, if (getIMGQRCode(url, qr)) { qr = NULL; } + + char* intro = + reauth_intro ? oidc_sprintf(intro_fmt, shortname) : oidc_strcopy(""); + cJSON* data = generateJSONObject("intro", cJSON_String, intro, "url", + cJSON_String, url, NULL); + data = jsonAddStringValue(data, "code", oidc_device_getUserCode(*device)); + if (qr != NULL) { + data = jsonAddBoolValue(data, "qr", cJSON_True); + } + char* text = getprompt(PROMPTTEMPLATE(AUTHENTICATE), data); secFree(intro); - displayLinkGUI(text, url, qr); + secFreeJson(data); + displayLinkGUI(text, NULL, qr); secFree(text); } -void agent_displayAuthCodeURL(const char* url, const char* shortname) { - char* intro = oidc_sprintf(intro_fmt, shortname); - char* text = oidc_sprintf( - "%sTo continue please open the following URL in your browser:\n", intro); +void agent_displayAuthCodeURL(const char* url, const char* shortname, + unsigned char reauth_intro) { + char* intro = + reauth_intro ? oidc_sprintf(intro_fmt, shortname) : oidc_strcopy(""); + cJSON* data = generateJSONObject("intro", cJSON_String, intro, "url", + cJSON_String, url, NULL); + char* text = getprompt(PROMPTTEMPLATE(AUTHENTICATE), data); secFree(intro); - displayLinkGUI(text, url, NULL); + secFreeJson(data); + displayLinkGUI(text, NULL, NULL); secFree(text); } diff --git a/src/oidc-agent/oidcp/passwords/agent_prompt.h b/src/oidc-agent/oidcp/passwords/agent_prompt.h index 2976a1a5..31c267ba 100644 --- a/src/oidc-agent/oidcp/passwords/agent_prompt.h +++ b/src/oidc-agent/oidcp/passwords/agent_prompt.h @@ -2,7 +2,7 @@ #define OIDCP_AGENT_PROMPT_H #include "oidc-agent/oidc/device_code.h" -#include "utils/prompt.h" +#include "utils/prompting/prompt.h" #define AGENT_PROMPT_TIMEOUT PROMPT_DEFAULT_TIMEOUT @@ -11,8 +11,9 @@ char* agent_promptPassword(const char* text, const char* label, int agent_promptConsentDefaultYes(const char* text); char* agent_promptMytokenConsent(const char* base64_html); -void agent_displayAuthCodeURL(const char* url, const char* shortname); +void agent_displayAuthCodeURL(const char* url, const char* shortname, + unsigned char reauth_intro); void agent_displayDeviceCode(const struct oidc_device_code* device, - const char* shortname); + const char* shortname, unsigned char reauth_intro); #endif /* OIDCP_AGENT_PROMPT_H */ diff --git a/src/oidc-agent/oidcp/passwords/askpass.c b/src/oidc-agent/oidcp/passwords/askpass.c index bbbadfca..c9422078 100644 --- a/src/oidc-agent/oidcp/passwords/askpass.c +++ b/src/oidc-agent/oidcp/passwords/askpass.c @@ -2,8 +2,10 @@ #include "oidc-agent/oidcp/passwords/agent_prompt.h" #include "utils/agentLogger.h" +#include "utils/json.h" #include "utils/memory.h" #include "utils/oidc_error.h" +#include "utils/prompting/getprompt.h" #include "utils/string/stringUtils.h" char* askpass_getPasswordForUpdate(const char* shortname) { @@ -15,10 +17,9 @@ char* askpass_getPasswordForUpdate(const char* shortname) { DEBUG, "Prompting user for encryption password for updating account config '%s'", shortname); - const char* const fmt = - "oidc-agent needs to update the account config for '%s'.\nPlease enter " - "the encryption password for '%s':"; - char* msg = oidc_sprintf(fmt, shortname, shortname); + cJSON* data = generateJSONObject("shortname", cJSON_String, shortname, NULL); + char* msg = getprompt(PROMPTTEMPLATE(UPDATE_ACCOUNT), data); + secFreeJson(data); char* ret = agent_promptPassword(msg, "Encryption password", NULL); secFree(msg); if (ret == NULL) { @@ -36,16 +37,10 @@ char* askpass_getPasswordForAutoload(const char* shortname, agent_log(DEBUG, "Prompting user for encryption password for autoload config '%s'", shortname); - const char* const fmt = - "An application %srequests an access token for '%s'.\nThis configuration " - "is currently not loaded.\nTo load '%s' into oidc-agent please enter " - "the encryption password for '%s':"; - char* application_str = strValid(application_hint) - ? oidc_sprintf("(%s) ", application_hint) - : NULL; - char* msg = - oidc_sprintf(fmt, application_str ?: "", shortname, shortname, shortname); - secFree(application_str); + cJSON* data = generateJSONObject("shortname", cJSON_String, shortname, NULL); + data = jsonAddStringValue(data, "application-hint", application_hint); + char* msg = getprompt(PROMPTTEMPLATE(UNLOCK_ACCOUNT), data); + secFreeJson(data); char* ret = agent_promptPassword(msg, "Encryption password", NULL); secFree(msg); if (ret == NULL) { @@ -66,17 +61,11 @@ char* askpass_getPasswordForAutoloadWithIssuer(const char* issuer, "Prompting user for encryption password for autoload config '%s' for " "issuer '%s'", shortname, issuer); - const char* const fmt = - "An application %srequests an access token for '%s',\nbut there's " - "currently no account configuration loaded for this provider.\nThe " - "default account configuration for this provider is '%s'.\nTo load '%s' " - "into oidc-agent please enter the encryption password for '%s':"; - char* application_str = strValid(application_hint) - ? oidc_sprintf("(%s) ", application_hint) - : NULL; - char* msg = oidc_sprintf(fmt, application_str ?: "", issuer, shortname, - shortname, shortname); - secFree(application_str); + cJSON* data = generateJSONObject("shortname", cJSON_String, shortname, + "issuer", cJSON_String, issuer, NULL); + data = jsonAddStringValue(data, "application-hint", application_hint); + char* msg = getprompt(PROMPTTEMPLATE(UNLOCK_ACCOUNT_ISSUER), data); + secFreeJson(data); char* ret = agent_promptPassword(msg, "Encryption password", NULL); secFree(msg); if (ret == NULL) { @@ -85,6 +74,25 @@ char* askpass_getPasswordForAutoloadWithIssuer(const char* issuer, return ret; } +oidc_error_t _askpass_getConfirmation(const char* issuer, const char* shortname, + const char* application_hint, + const char* token_type) { + cJSON* data = + generateJSONObject("shortname", cJSON_String, shortname, "token-type", + cJSON_String, token_type, NULL); + data = jsonAddStringValue(data, "issuer", issuer); + data = jsonAddStringValue(data, "application-hint", application_hint); + if (strcaseequal(token_type, "id")) { + data = jsonAddBoolValue(data, "id", cJSON_True); + } + char* msg = getprompt(PROMPTTEMPLATE(CONFIRM), data); + secFreeJson(data); + oidc_errno = + agent_promptConsentDefaultYes(msg) ? OIDC_SUCCESS : OIDC_EFORBIDDEN; + secFree(msg); + return oidc_errno; +} + oidc_error_t askpass_getConfirmation(const char* shortname, const char* application_hint) { if (shortname == NULL) { @@ -93,18 +101,7 @@ oidc_error_t askpass_getConfirmation(const char* shortname, } agent_log(DEBUG, "Prompting user for confirmation of using config '%s'", shortname); - const char* const fmt = - "An application %srequests an access token for '%s'.\n" - "Do you want to allow this usage?"; - char* application_str = strValid(application_hint) - ? oidc_sprintf("(%s) ", application_hint) - : NULL; - char* msg = oidc_sprintf(fmt, application_str ?: "", shortname); - secFree(application_str); - oidc_errno = - agent_promptConsentDefaultYes(msg) ? OIDC_SUCCESS : OIDC_EFORBIDDEN; - secFree(msg); - return oidc_errno; + return _askpass_getConfirmation(NULL, shortname, application_hint, "access"); } oidc_error_t askpass_getConfirmationWithIssuer(const char* issuer, @@ -118,18 +115,8 @@ oidc_error_t askpass_getConfirmationWithIssuer(const char* issuer, DEBUG, "Prompting user for confirmation of using config '%s' for issuer '%s'", shortname, issuer); - const char* const fmt = - "An application %srequests an access token for '%s'.\n" - "Do you want to allow the usage of '%s'?"; - char* application_str = strValid(application_hint) - ? oidc_sprintf("(%s) ", application_hint) - : NULL; - char* msg = oidc_sprintf(fmt, application_str ?: "", issuer, shortname); - secFree(application_str); - oidc_errno = - agent_promptConsentDefaultYes(msg) ? OIDC_SUCCESS : OIDC_EFORBIDDEN; - secFree(msg); - return oidc_errno; + return _askpass_getConfirmation(issuer, shortname, application_hint, + "access"); } oidc_error_t askpass_getIdTokenConfirmation(const char* shortname, @@ -140,19 +127,7 @@ oidc_error_t askpass_getIdTokenConfirmation(const char* shortname, } agent_log(DEBUG, "Prompting user for id-token confirmation for config '%s'", shortname); - const char* const fmt = - "An application %srequests an id token for '%s'.\n" - "id tokens should not be passed to other applications as authorization.\n" - "Do you want to allow this usage?"; - char* application_str = strValid(application_hint) - ? oidc_sprintf("(%s) ", application_hint) - : NULL; - char* msg = oidc_sprintf(fmt, application_str ?: "", shortname); - secFree(application_str); - oidc_errno = - agent_promptConsentDefaultYes(msg) ? OIDC_SUCCESS : OIDC_EFORBIDDEN; - secFree(msg); - return oidc_errno; + return _askpass_getConfirmation(NULL, shortname, application_hint, "id"); } oidc_error_t askpass_getIdTokenConfirmationWithIssuer( @@ -161,20 +136,7 @@ oidc_error_t askpass_getIdTokenConfirmationWithIssuer( "Prompting user for id-token confirmation for " "issuer '%s'", issuer); - const char* const fmt = - "An application %srequests an id token for '%s'.\n" - "id tokens should not be passed to other applications as authorization.\n" - "Do you want to allow the usage of '%s'?"; - char* application_str = strValid(application_hint) - ? oidc_sprintf("(%s) ", application_hint) - : NULL; - char* msg = - oidc_sprintf(fmt, application_str ?: "", issuer, shortname ?: issuer); - secFree(application_str); - oidc_errno = - agent_promptConsentDefaultYes(msg) ? OIDC_SUCCESS : OIDC_EFORBIDDEN; - secFree(msg); - return oidc_errno; + return _askpass_getConfirmation(issuer, shortname, application_hint, "id"); } char* askpass_getMytokenConfirmation(const char* base64_html) { diff --git a/src/oidc-agent/oidcp/passwords/keyring.c b/src/oidc-agent/oidcp/passwords/keyring.c deleted file mode 100644 index 534bcb8d..00000000 --- a/src/oidc-agent/oidcp/passwords/keyring.c +++ /dev/null @@ -1,110 +0,0 @@ -#ifndef __APPLE__ -#include "keyring.h" - -#include - -#include "utils/agentLogger.h" -#include "utils/oidc_error.h" -#include "utils/string/stringUtils.h" - -const SecretSchema* agent_get_schema(void) G_GNUC_CONST; - -#define AGENT_SCHEMA agent_get_schema() - -const SecretSchema* agent_get_schema(void) { - static const SecretSchema the_schema = { - "edu.kit.oidc-agent.Password", - SECRET_SCHEMA_NONE, - { - {"shortname", SECRET_SCHEMA_ATTRIBUTE_STRING}, - {"NULL", 0}, - }, - // These are just reserved variables - 0, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL}; - return &the_schema; -} - -void oidc_setGerror(GError* error) { - if (error == NULL) { - return; - } - agent_log(ERROR, "%s", error->message); - oidc_errno = OIDC_EGERROR; - oidc_seterror(error->message); -} - -char* keyring_getPasswordFor(const char* shortname) { - if (shortname == NULL) { - oidc_setArgNullFuncError(__func__); - return NULL; - } - agent_log(DEBUG, "Looking up password for '%s' in keyring", shortname); - GError* error = NULL; - gchar* pw = secret_password_lookup_sync(AGENT_SCHEMA, NULL, &error, - "shortname", shortname, NULL); - if (error != NULL) { - oidc_setGerror(error); - g_error_free(error); - return NULL; - } - if (pw == NULL) { - agent_log(DEBUG, "No password found for '%s' in keyring", shortname); - oidc_errno = OIDC_EPWNOTFOUND; - return NULL; - } - char* ret = oidc_strcopy(pw); - secret_password_free(pw); - return ret; -} - -oidc_error_t keyring_savePasswordFor(const char* shortname, - const char* password) { - if (shortname == NULL || password == NULL) { - oidc_setArgNullFuncError(__func__); - return oidc_errno; - } - agent_log(DEBUG, "Saving password for '%s' in keyring", shortname); - GError* error = NULL; - secret_password_store_sync(AGENT_SCHEMA, SECRET_COLLECTION_DEFAULT, shortname, - password, NULL, &error, "shortname", shortname, - NULL); - - if (error == NULL) { - agent_log(DEBUG, "Password for '%s' saved in keyring", shortname); - return OIDC_SUCCESS; - } - oidc_setGerror(error); - g_error_free(error); - return oidc_errno; -} - -oidc_error_t keyring_removePasswordFor(const char* shortname) { - if (shortname == NULL) { - oidc_setArgNullFuncError(__func__); - return oidc_errno; - } - agent_log(DEBUG, "Removing password for '%s' from keyring", shortname); - GError* error = NULL; - gboolean removed = secret_password_clear_sync(AGENT_SCHEMA, NULL, &error, - "shortname", shortname, NULL); - - if (error != NULL) { - oidc_setGerror(error); - g_error_free(error); - return oidc_errno; - } - if (removed) { - agent_log(DEBUG, "Password for '%s' removed from keyring", shortname); - } else { - agent_log(DEBUG, "No password to remove for '%s' in keyring", shortname); - } - return OIDC_SUCCESS; -} -#endif diff --git a/src/oidc-agent/oidcp/passwords/keyring.h b/src/oidc-agent/oidcp/passwords/keyring.h deleted file mode 100644 index ac0efc8d..00000000 --- a/src/oidc-agent/oidcp/passwords/keyring.h +++ /dev/null @@ -1,11 +0,0 @@ -#ifndef OIDCAGENT_KEYRING_INTEGRATION_H -#define OIDCAGENT_KEYRING_INTEGRATION_H - -#include "utils/oidc_error.h" - -oidc_error_t keyring_savePasswordFor(const char* shortname, - const char* password); -char* keyring_getPasswordFor(const char* shortname); -oidc_error_t keyring_removePasswordFor(const char* shortname); - -#endif // OIDCAGENT_KEYRING_INTEGRATION_H diff --git a/src/oidc-agent/oidcp/passwords/password_handler.c b/src/oidc-agent/oidcp/passwords/password_handler.c index 4394befa..95153b66 100644 --- a/src/oidc-agent/oidcp/passwords/password_handler.c +++ b/src/oidc-agent/oidcp/passwords/password_handler.c @@ -5,16 +5,12 @@ #include "utils/oidc_error.h" #include "utils/password_entry.h" -oidc_error_t pw_handleSave(const char* pw_entry_str, - const struct lifetimeArg pw_lifetime) { +oidc_error_t pw_handleSave(const char* pw_entry_str) { if (pw_entry_str == NULL) { oidc_setArgNullFuncError(__func__); return oidc_errno; } struct password_entry* pw = JSONStringToPasswordEntry(pw_entry_str); - if (pwe_getExpiresAt(pw) == 0 && pw_lifetime.argProvided) { - pwe_setExpiresIn(pw, pw_lifetime.lifetime); - } if (!pw->shortname) { oidc_setInternalError("shortname not set in pw_entry"); agent_log(ERROR, "%s", oidc_serror()); @@ -25,7 +21,7 @@ oidc_error_t pw_handleSave(const char* pw_entry_str, agent_log(ERROR, "%s", oidc_serror()); return oidc_errno; } - if (!pw->password && (pw->type & (PW_TYPE_MNG | PW_TYPE_MEM))) { + if (!pw->password && (pw->type & PW_TYPE_MEM)) { oidc_setInternalError("password not set in pw_entry"); agent_log(ERROR, "%s", oidc_serror()); return oidc_errno; diff --git a/src/oidc-agent/oidcp/passwords/password_handler.h b/src/oidc-agent/oidcp/passwords/password_handler.h index f6446ff3..2f838cc1 100644 --- a/src/oidc-agent/oidcp/passwords/password_handler.h +++ b/src/oidc-agent/oidcp/passwords/password_handler.h @@ -4,6 +4,5 @@ #include "utils/lifetimeArg.h" #include "utils/oidc_error.h" -oidc_error_t pw_handleSave(const char* pw_entry_str, - const struct lifetimeArg pw_lifetime); +oidc_error_t pw_handleSave(const char* pw_entry_str); #endif // OIDC_PASSWORD_HANDLER_H diff --git a/src/oidc-agent/oidcp/passwords/password_store.c b/src/oidc-agent/oidcp/passwords/password_store.c index c6018351..a67d7f91 100644 --- a/src/oidc-agent/oidcp/passwords/password_store.c +++ b/src/oidc-agent/oidcp/passwords/password_store.c @@ -1,11 +1,8 @@ #include "password_store.h" -#include "oidc-agent/oidcp/passwords/askpass.h" -#ifndef __APPLE__ -#include "oidc-agent/oidcp/passwords/keyring.h" -#endif #include +#include "oidc-agent/oidcp/passwords/askpass.h" #include "utils/agentLogger.h" #include "utils/crypt/passwordCrypt.h" #include "utils/db/password_db.h" @@ -50,7 +47,7 @@ char* memory_getPasswordFor(const struct password_entry* pwe) { void initPasswordStore() { passwordDB_new(); passwordDB_setMatchFunction((matchFunction)matchPasswordEntryByShortname); - passwordDB_setFreeFunction((void(*)(void*))_secFreePasswordEntry); + passwordDB_setFreeFunction((void (*)(void*))_secFreePasswordEntry); } oidc_error_t savePassword(struct password_entry* pw) { @@ -88,13 +85,6 @@ oidc_error_t savePassword(struct password_entry* pw) { } pwe_setGPGKey(pw, tmp); } - if (pw->type & PW_TYPE_MNG) { -#ifndef __APPLE__ - keyring_savePasswordFor(pw->shortname, pw->password); -#else - agent_log(WARNING, "keyring currently not supported for MACOS"); -#endif - } passwordDB_removeIfFound( pw); // Removing an existing (old) entry for the same shortname -> update passwordDB_addValue(pw); @@ -116,14 +106,6 @@ oidc_error_t removeOrExpirePasswordFor(const char* shortname, int remove) { agent_log(DEBUG, "No password found for '%s'", shortname); return OIDC_SUCCESS; } - unsigned char type = pw->type; - if (type & PW_TYPE_MNG) { -#ifndef __APPLE__ - keyring_removePasswordFor(shortname); -#else - agent_log(WARNING, "keyring currently not supported for MACOS"); -#endif - } if (remove) { passwordDB_removeIfFound(pw); } else { @@ -190,16 +172,6 @@ char* getPasswordFor(const char* shortname) { res = decryptPassword(crypt, shortname); secFree(crypt); } - if (!res && type & PW_TYPE_MNG) { -#ifndef __APPLE__ - agent_log(DEBUG, "Try getting password from keyring"); - char* crypt = keyring_getPasswordFor(shortname); - res = decryptPassword(crypt, shortname); - secFree(crypt); -#else - agent_log(WARNING, "keyring currently not supported for MACOS"); -#endif - } if (!res && type & PW_TYPE_CMD) { agent_log(DEBUG, "Try getting password from command"); char* cmd = decryptPassword(pw->command, shortname); diff --git a/src/oidc-agent/oidcp/proxy_handler.c b/src/oidc-agent/oidcp/proxy_handler.c index 7cb3bc3e..bae8cc98 100644 --- a/src/oidc-agent/oidcp/proxy_handler.c +++ b/src/oidc-agent/oidcp/proxy_handler.c @@ -2,13 +2,14 @@ #include -#include "account/issuer_helper.h" #include "defines/settings.h" #include "oidc-agent/oidcp/passwords/askpass.h" #include "oidc-agent/oidcp/passwords/password_store.h" +#include "utils/config/issuerConfig.h" #include "utils/crypt/cryptUtils.h" #include "utils/crypt/gpg/gpg.h" #include "utils/file_io/oidc_file_io.h" +#include "utils/json.h" #include "utils/listUtils.h" #include "utils/string/stringUtils.h" @@ -75,40 +76,52 @@ char* getAutoloadConfig(const char* shortname, const char* issuer, return NULL; } char* config = decryptFileContent(crypt_content, password); - secFree(password); - if (config != NULL) { - secFree(crypt_content); - return config; + if (config == NULL) { + secFree(password); + continue; + } + + char* issFromConfig = NULL; + if (issuer == NULL) { + issFromConfig = getJSONValueFromString(config, OIDC_KEY_ISSUER); + if (issFromConfig == NULL) { + issFromConfig = getJSONValueFromString(config, AGENT_KEY_ISSUERURL); + } + issuer = issFromConfig; } + const struct issuerConfig* iss_c = getIssuerConfig(issuer); + secFree(issFromConfig); + if (iss_c && iss_c->store_pw) { + struct password_entry* pw = secAlloc(sizeof(struct password_entry)); + pwe_setShortname(pw, oidc_strcopy(shortname)); + pwe_setPassword(pw, oidc_strcopy(password)); + pwe_setType(pw, PW_TYPE_PRMT | PW_TYPE_MEM); + savePassword(pw); + } + + secFree(password); + secFree(crypt_content); + return config; } secFree(crypt_content); return NULL; } -char* getDefaultAccountConfigForIssuer(const char* issuer_url) { +const char* getDefaultAccountConfigForIssuer(const char* issuer_url) { if (issuer_url == NULL) { oidc_setArgNullFuncError(__func__); return NULL; } - list_t* issuers = getLinesFromOidcFile(ISSUER_CONFIG_FILENAME); - if (issuers == NULL) { + const struct issuerConfig* c = getIssuerConfig(issuer_url); + if (c == NULL) { return NULL; } - char* shortname = NULL; - list_node_t* node; - list_iterator_t* it = list_iterator_new(issuers, LIST_HEAD); - while ((node = list_iterator_next(it))) { - char* line = node->val; - char* iss = strtok(line, " "); - char* acc = strtok(NULL, " "); - if (compIssuerUrls(issuer_url, iss)) { - if (strValid(acc)) { - shortname = oidc_strcopy(acc); - } - break; - } + if (strValid(c->default_account)) { + return c->default_account; + } + if (!listValid(c->accounts)) { + return NULL; } - list_iterator_destroy(it); - secFreeList(issuers); - return shortname; + list_node_t* firstAccount = list_at(c->accounts, 0); + return firstAccount ? firstAccount->val : NULL; } diff --git a/src/oidc-agent/oidcp/proxy_handler.h b/src/oidc-agent/oidcp/proxy_handler.h index 9aad9020..9466f2b4 100644 --- a/src/oidc-agent/oidcp/proxy_handler.h +++ b/src/oidc-agent/oidcp/proxy_handler.h @@ -15,6 +15,6 @@ oidc_error_t updateRefreshTokenUsingGPG(const char* shortname, const char* gpg_key); char* getAutoloadConfig(const char* shortname, const char* issuer, const char* application_hint); -char* getDefaultAccountConfigForIssuer(const char* issuer_url); +const char* getDefaultAccountConfigForIssuer(const char* issuer_url); #endif // OIDC_PROXY_HANDLER_H diff --git a/src/oidc-agent/stats/statid.c b/src/oidc-agent/stats/statid.c new file mode 100644 index 00000000..94b15392 --- /dev/null +++ b/src/oidc-agent/stats/statid.c @@ -0,0 +1,149 @@ +#ifndef NO_STATLOG +#include "statid.h" + +#include "defines/msys.h" +#include "oidc-agent/http/http_ipc.h" +#include "utils/config/agent_config.h" +#include "utils/crypt/crypt.h" +#include "utils/file_io/file_io.h" +#include "utils/file_io/oidc_file_io.h" +#include "utils/memory.h" +#include "utils/string/stringUtils.h" +#include "utils/system_runner.h" +#include "utils/tempenv.h" + +#ifndef ANY_MSYS +#include +#include +#include +#endif + +static char* tmpBootIdFilePath() { + const char* tmpdir = get_tmp_env(); + if (!strValid(tmpdir)) { + tmpdir = "/tmp"; + } + char* filename = +#ifdef ANY_MSYS + oidc_strcopy("oidc-agent-boot_id"); +#else + oidc_sprintf("oidc-agent-boot_id_%d", getuid()); +#endif + + char* path = oidc_pathcat(tmpdir, filename); + secFree(filename); + return path; +} + +static char* getBootID() { + char* b = readFile("/proc/sys/kernel/random/boot_id"); + if (strValid(b)) { + return b; + } + char* tmpBPath = tmpBootIdFilePath(); + secFree(b); + b = readFile(tmpBPath); + if (strValid(b)) { + secFree(tmpBPath); + return b; + } + secFree(b); + b = randomString(24); + writeFile(tmpBPath, b); + secFree(tmpBPath); + return b; +} + +static char* getMachineID() { + char* m = readFile("/etc/machine-id"); + if (strValid(m)) { + return m; + } + const char* const ownIDFileName = "machine_id.config"; + secFree(m); + m = readOidcFile(ownIDFileName); + if (strValid(m)) { + return m; + } + secFree(m); + m = randomString(24); + writeOidcFile(ownIDFileName, m); + return m; +} + +static char* getOSInfo() { +#ifndef ANY_MSYS + return getOutputFromCommand("uname -sorvm"); +#else + return oidc_strcopy("windows"); +#endif +} + +static time_t location_updated = 0; + +static char* getLocation() { + char* code = + httpsGET("http://ip-api.com/line/?fields=countryCode", NULL, NULL); + if (code) { + location_updated = time(NULL); + if (lastChar(code) == '\n') { + lastChar(code) = '\0'; + } + } + return code; +} + +static struct statid* id = NULL; + +#define ONE_DAY 86400 + +static void upd_location() { + if (id == NULL) { + return; + } + time_t now = time(NULL); + if (now < location_updated + ONE_DAY) { + return; + } + char* code = getLocation(); + if (code) { + secFree(id->location); + id->location = code; + } +} + +struct statid getStatID() { + if (id != NULL) { + upd_location(); + return *id; + } + char* b = getBootID(); + char* m = getMachineID(); + if (lastChar(b) == '\n') { + lastChar(b) = '\0'; + } + if (lastChar(m) == '\n') { + lastChar(m) = '\0'; + } + id = secAlloc(sizeof(struct statid)); + *id = (struct statid){ +#ifdef ANY_MSYS + .machine_id = m, + .boot_id = b, +#else + .machine_id = oidc_sprintf("%s_%d", m, getuid()), + .boot_id = oidc_sprintf("%s_%d", b, getuid()), +#endif + .os_info = getOSInfo(), + }; + if (getAgentConfig()->stats_collect_location) { + id->location = getLocation(); + } +#ifndef ANY_MSYS + secFree(b); + secFree(m); +#endif + return *id; +} + +#endif // NO_STATLOG \ No newline at end of file diff --git a/src/oidc-agent/stats/statid.h b/src/oidc-agent/stats/statid.h new file mode 100644 index 00000000..f7c930cd --- /dev/null +++ b/src/oidc-agent/stats/statid.h @@ -0,0 +1,15 @@ +#ifndef NO_STATLOG +#ifndef OIDC_AGENT_STATID_H +#define OIDC_AGENT_STATID_H + +struct statid { + char* machine_id; + char* boot_id; + char* os_info; + char* location; +}; + +struct statid getStatID(); + +#endif // OIDC_AGENT_STATID_H +#endif // NO_STATLOG \ No newline at end of file diff --git a/src/oidc-agent/stats/statlogger.c b/src/oidc-agent/stats/statlogger.c new file mode 100644 index 00000000..2c209d0b --- /dev/null +++ b/src/oidc-agent/stats/statlogger.c @@ -0,0 +1,171 @@ +#define _XOPEN_SOURCE +#include "statlogger.h" + +#ifndef NO_STATLOG +#include +#include + +#include "defines/ipc_values.h" +#include "oidc-agent/http/http_ipc.h" +#include "statid.h" +#include "utils/config/agent_config.h" +#include "utils/file_io/oidc_file_io.h" +#include "utils/json.h" +#include "utils/key_value.h" +#include "utils/listUtils.h" +#include "utils/string/stringUtils.h" + +static unsigned char called_tz_set = 0; + +static char* createMsg(const char* ipc_request) { + if (!called_tz_set) { + tzset(); + called_tz_set = 1; + } + INIT_KEY_VALUE(IPC_KEY_REQUEST, IPC_KEY_SHORTNAME, IPC_KEY_MINVALID, + IPC_KEY_CONFIG, IPC_KEY_FLOW, OIDC_KEY_SCOPE, IPC_KEY_LIFETIME, + IPC_KEY_APPLICATIONHINT, IPC_KEY_ISSUERURL, IPC_KEY_AUDIENCE, + IPC_KEY_ONLYAT, AGENT_KEY_MYTOKENPROFILE, + AGENT_KEY_CONFIG_ENDPOINT, INT_IPC_KEY_ACTION); + if (getJSONValuesFromString(ipc_request, pairs, + sizeof(pairs) / sizeof(*pairs)) < 0) { + secFreeKeyValuePairs(pairs, sizeof(pairs) / sizeof(*pairs)); + return NULL; + } + KEY_VALUE_VARS(request, shortname, minvalid, config, flow, scope, lifetime, + applicationHint, issuer, audience, only_at, profile, + config_endpoint, action); + char* mytoken_url = NULL; + if (_config != NULL) { + if (_issuer == NULL) { + _issuer = getJSONValueFromString(_config, OIDC_KEY_ISSUER); + if (_issuer == NULL) { + _issuer = getJSONValueFromString(_config, AGENT_KEY_ISSUERURL); + } + } + if (_shortname == NULL) { + _shortname = getJSONValueFromString(_config, AGENT_KEY_SHORTNAME); + } + mytoken_url = getJSONValueFromString(_config, AGENT_KEY_MYTOKENURL); + } + const struct statid id = getStatID(); + cJSON* json = generateJSONObject( + "machine_id", cJSON_String, id.machine_id, "boot_id", cJSON_String, + id.boot_id, "os_info", cJSON_String, id.os_info, "time", cJSON_Number, + time(NULL), "lt_offset", cJSON_Number, (daylight ? 3600 : 0) - timezone, + "agent_version", cJSON_String, VERSION, NULL); + + if (id.location) { + jsonAddStringValue(json, "country", id.location); + } + if (_request) { + jsonAddStringValue(json, IPC_KEY_REQUEST, _request); + } + if (_issuer) { + jsonAddStringValue(json, IPC_KEY_ISSUERURL, _issuer); + } + if (_config_endpoint) { + jsonAddStringValue(json, AGENT_KEY_CONFIG_ENDPOINT, _config_endpoint); + } + if (mytoken_url) { + jsonAddStringValue(json, AGENT_KEY_MYTOKENURL, mytoken_url); + } + if (_shortname) { + jsonAddStringValue(json, IPC_KEY_SHORTNAME, _shortname); + } + if (_applicationHint) { + jsonAddStringValue(json, IPC_KEY_APPLICATIONHINT, _applicationHint); + } + if (_flow) { + jsonAddArrayValue(json, IPC_KEY_FLOW, _flow); + } + if (_scope) { + jsonAddStringValue(json, OIDC_KEY_SCOPE, _scope); + } + if (_audience) { + jsonAddStringValue(json, IPC_KEY_AUDIENCE, _audience); + } + if (_minvalid) { + jsonAddNumberValue(json, IPC_KEY_MINVALID, strToInt(_minvalid)); + } + if (_lifetime) { + jsonAddNumberValue(json, IPC_KEY_LIFETIME, strToInt(_lifetime)); + } + if (_only_at) { + jsonAddBoolValue(json, IPC_KEY_ONLYAT, strToInt(_only_at)); + } + if (_profile) { + jsonAddStringValue(json, AGENT_KEY_MYTOKENPROFILE, _profile); + } + if (_action) { + jsonAddStringValue(json, INT_IPC_KEY_ACTION, _action); + } + SEC_FREE_KEY_VALUES(); + char* msg = jsonToStringUnformatted(json); + secFreeJson(json); + return msg; +} + +static time_t lastSendTime = 0; +#define ONE_DAY 86400 + +static size_t charPos = 0; + +#ifndef SYNC_BLOCK +#define SYNC_BLOCK "### SYNC BLOCK ###" +#endif +#ifndef STATS_FILE +#define STATS_FILE "oidc-agent.stats" +#endif +#ifndef STATS_SERVER +#define STATS_SERVER "https://oidc-agent.test.fedcloud.eu" +#endif + +static oidc_error_t sendStatPayload(const char* payload) { + char* res = sendJSONPostWithoutBasicAuth(STATS_SERVER, payload, NULL, NULL); + oidc_error_t ret = + strcaseequal(res, "Thank you!") ? OIDC_SUCCESS : OIDC_EERROR; + secFree(res); + return ret; +} + +static void sendStats() { + if (!getAgentConfig()->stats_collect_share) { + return; + } + time_t now = time(NULL); + if (now < lastSendTime + ONE_DAY) { + return; + } + char* new_stats = getFileContentFromOidcFileAfterLine(STATS_FILE, SYNC_BLOCK, + (long)charPos, 1); + if (!strValid(new_stats)) { + secFree(new_stats); + return; + } + if (strCountChar(new_stats, '\n') < 10) { + secFree(new_stats); + return; + } + char* jsonStats = delimitedStringToJSONArrayFmt(new_stats, '\n', "%s"); + if (sendStatPayload(jsonStats) == OIDC_SUCCESS) { + charPos += strlen(new_stats); + lastSendTime = now; + appendOidcFile(STATS_FILE, SYNC_BLOCK); + } + secFree(jsonStats); + secFree(new_stats); +} + +#endif // NO_STATLOG + +void statlog(const char* ipc_request) { +#ifndef NO_STATLOG + if (!getAgentConfig()->stats_collect) { + return; + } + char* msg = createMsg(ipc_request); + appendOidcFile(STATS_FILE, msg); + sendStats(); +#endif // NO_STATLOG +} diff --git a/src/oidc-agent/stats/statlogger.h b/src/oidc-agent/stats/statlogger.h new file mode 100644 index 00000000..5cc47543 --- /dev/null +++ b/src/oidc-agent/stats/statlogger.h @@ -0,0 +1,6 @@ +#ifndef OIDC_AGENT_STATLOGGER_H +#define OIDC_AGENT_STATLOGGER_H + +void statlog(const char* ipc_request); + +#endif // OIDC_AGENT_STATLOGGER_H diff --git a/src/oidc-gen/gen_consenter.c b/src/oidc-gen/gen_consenter.c index ce85b8ac..6634374b 100644 --- a/src/oidc-gen/gen_consenter.c +++ b/src/oidc-gen/gen_consenter.c @@ -1,7 +1,7 @@ #include "gen_consenter.h" -#include "utils/prompt.h" -#include "utils/prompt_mode.h" +#include "utils/prompting/prompt.h" +#include "utils/prompting/prompt_mode.h" int _gen_prompter(const char* prompt, const struct arguments* arguments, int (*consentFnc)(const char*)) { diff --git a/src/oidc-gen/gen_handler.c b/src/oidc-gen/gen_handler.c index e16a4fa5..36ea0e7e 100644 --- a/src/oidc-gen/gen_handler.c +++ b/src/oidc-gen/gen_handler.c @@ -26,6 +26,8 @@ #include "oidc-gen/parse_ipc.h" #include "oidc-gen/promptAndSet/promptAndSet.h" #include "utils/accountUtils.h" +#include "utils/config/gen_config.h" +#include "utils/config/issuerConfig.h" #include "utils/crypt/crypt.h" #include "utils/crypt/gpg/gpg.h" #include "utils/errorUtils.h" @@ -36,12 +38,13 @@ #include "utils/listUtils.h" #include "utils/logger.h" #include "utils/oidc/device.h" -#include "utils/oidc/oidcUtils.h" #include "utils/parseJson.h" +#include "utils/pass.h" #include "utils/password_entry.h" #include "utils/printer.h" -#include "utils/prompt.h" -#include "utils/promptUtils.h" +#include "utils/prompting/getprompt.h" +#include "utils/prompting/prompt.h" +#include "utils/prompting/promptUtils.h" #include "utils/string/stringUtils.h" #include "utils/uriUtils.h" #ifdef __MSYS__ @@ -61,7 +64,7 @@ char* _gen_response(struct oidc_account* account, readConfigEndpoint(account, arguments); readDeviceAuthEndpoint(account, arguments); readAudience(account, arguments); - cJSON* flow_json = listToJSONArray(arguments->flows); + cJSON* flow_json = stringListToJSONArray(arguments->flows); char* log_tmp = jsonToString(flow_json); logger(DEBUG, "arguments flows in handleGen are '%s'", log_tmp); secFree(log_tmp); @@ -112,7 +115,7 @@ char* _gen_response(struct oidc_account* account, } } if (arguments->pw_gpg) { - pwe_setFile(&pw, arguments->pw_gpg); + pwe_setGPGKey(&pw, arguments->pw_gpg); type |= PW_TYPE_GPG; } pwe_setType(&pw, type); @@ -143,9 +146,6 @@ void handleGen(struct oidc_account* account, const struct arguments* arguments, char* json = _gen_response(account, arguments); char* issuer = getJSONValueFromString(json, AGENT_KEY_ISSUERURL); char* name = getJSONValueFromString(json, AGENT_KEY_SHORTNAME); - if (!arguments->noSave) { - updateIssuerConfig(issuer, name); - } secFree(issuer); char* hint = oidc_sprintf("account configuration '%s'", name); gen_saveAccountConfig(json, account_getName(account), hint, @@ -157,19 +157,31 @@ void handleGen(struct oidc_account* account, const struct arguments* arguments, secFree(json); } -void manualGen(struct oidc_account* account, - const struct arguments* arguments) { +void manualGen(struct oidc_account* account, const struct arguments* arguments, + unsigned char on_mytoken_preferred_but_fails_return) { if (arguments == NULL) { oidc_setArgNullFuncError(__func__); oidc_perror(); exit(EXIT_FAILURE); } - char* cryptPass = NULL; - char** cryptPassPtr = &cryptPass; - account = manual_genNewAccount(account, arguments, cryptPassPtr); - cryptPass = *cryptPassPtr; + char* cryptPass = NULL; + char** cryptPassPtr = &cryptPass; + unsigned char on_mytoken_preferred_but_fails_return_copy = + on_mytoken_preferred_but_fails_return; + account = manual_genNewAccount(account, arguments, cryptPassPtr, + &on_mytoken_preferred_but_fails_return_copy); + cryptPass = *cryptPassPtr; + if (on_mytoken_preferred_but_fails_return_copy != + on_mytoken_preferred_but_fails_return) { // If mytoken failed and we + // should return the + // on_mytoken_preferred_but_fails_return_copy + // value was altered + secFree(cryptPass); + return; + } handleGen(account, arguments, cryptPass); secFree(cryptPass); + exit(EXIT_SUCCESS); } void reauthenticate(const char* shortname, struct arguments* arguments) { @@ -342,11 +354,10 @@ void handleCodeExchange(const struct arguments* arguments) { secFree(short_name); short_name = getJSONValueFromString(config, AGENT_KEY_SHORTNAME); } + char* msg = getprompt(PROMPTTEMPLATE(SHORTNAME), NULL); while (!strValid(short_name)) { secFree(short_name); - short_name = - prompt("Enter short name for the account to configure: ", "short name", - NULL, CLI_PROMPT_VERBOSE); + short_name = prompt(msg, "short name", NULL, CLI_PROMPT_VERBOSE); if (oidcFileDoesExist(short_name)) { if (!gen_promptConsentDefaultNo( "An account with that shortname already exists. Overwrite?", @@ -355,6 +366,7 @@ void handleCodeExchange(const struct arguments* arguments) { } } } + secFree(msg); char* hint = oidc_sprintf("account configuration '%s'", short_name); gen_saveAccountConfig(config, short_name, hint, NULL, arguments); secFree(hint); @@ -429,9 +441,6 @@ void stateLookUpWithConfigSave(const char* state, } char* issuer = getJSONValueFromString(config, AGENT_KEY_ISSUERURL); char* short_name = getJSONValueFromString(config, AGENT_KEY_SHORTNAME); - if (!arguments->noSave) { - updateIssuerConfig(issuer, short_name); - } secFree(issuer); char* hint = oidc_sprintf("account configuration '%s'", short_name); gen_saveAccountConfig(config, short_name, hint, NULL, arguments); @@ -463,9 +472,9 @@ char* gen_handleDeviceFlow(const char* json_device, return ret; } -struct oidc_account* manual_genNewAccount(struct oidc_account* account, - const struct arguments* arguments, - char** cryptPassPtr) { +struct oidc_account* manual_genNewAccount( + struct oidc_account* account, const struct arguments* arguments, + char** cryptPassPtr, unsigned char* on_mytoken_preferred_but_fails_return) { if (arguments == NULL) { oidc_setArgNullFuncError(__func__); oidc_perror(); @@ -478,7 +487,7 @@ struct oidc_account* manual_genNewAccount(struct oidc_account* account, } } if (!arguments->only_at) { - needName(account, arguments); + needName(account, !arguments->manual, arguments->args[0], arguments->cnid); char* shortname = account_getName(account); if (oidcFileDoesExist(shortname)) { struct resultWithEncryptionPassword result = @@ -516,15 +525,16 @@ struct oidc_account* manual_genNewAccount(struct oidc_account* account, } readCertPath(account, arguments); readConfigEndpoint(account, arguments); - if (MYTOKEN_USAGE_SET(arguments)) { + unsigned char only_preferred = + getGenConfig()->prefer_mytoken_over_oidc && !MYTOKEN_USAGE_SET(arguments); + if (MYTOKEN_USAGE_SET(arguments) || only_preferred) { needMytokenIssuer(account, arguments); char* providers = gen_handleMytokenProvidersLookup(account); list_t* providers_l = JSONArrayStringToList(providers); secFree(providers); list_t* iss_l = list_new(); - // list_t* scopes_l = list_new(); - // scopes_l->free = _secFree; - iss_l->free = _secFree; + iss_l->free = _secFree; + iss_l->match = (matchFunction)compIssuerUrls; list_node_t* node; list_iterator_t* it = list_iterator_new(providers_l, LIST_HEAD); unsigned char foundArgIss = 0; @@ -535,9 +545,8 @@ struct oidc_account* manual_genNewAccount(struct oidc_account* account, foundArgIss = 1; break; } - list_rpush(iss_l, list_node_new(iss)); - // list_rpush(scopes_l, list_node_new(getJSONValueFromString( - // p, OIDC_KEY_SCOPES_SUPPORTED))); + list_rpush(iss_l, + list_node_new(getJSONValueFromString(p, OIDC_KEY_ISSUER))); } list_iterator_destroy(it); secFreeList(providers_l); @@ -545,6 +554,13 @@ struct oidc_account* manual_genNewAccount(struct oidc_account* account, secFreeList(iss_l); if (foundArgIss) { account_setIssuerUrl(account, oidc_strcopy(arguments->issuer)); + } else if (only_preferred) { + if (on_mytoken_preferred_but_fails_return && + *on_mytoken_preferred_but_fails_return) { + (*on_mytoken_preferred_but_fails_return)++; + return account; + } + goto oidc; } else { char* e = oidc_sprintf("The specified issuer '%s' is not supported by " "this mytoken server.\n", @@ -553,25 +569,30 @@ struct oidc_account* manual_genNewAccount(struct oidc_account* account, secFree(e); exit(EXIT_FAILURE); } + } else if (only_preferred) { + needIssuer(account, arguments); + if (findInList(iss_l, account_getIssuerUrl(account))) { + // selected OP is supported by mytoken server + pass; + } else { + // selected OP is not supported by mytoken server, do normal oidc + secFreeList(iss_l); + if (on_mytoken_preferred_but_fails_return && + *on_mytoken_preferred_but_fails_return) { + (*on_mytoken_preferred_but_fails_return)++; + return account; + } + goto oidc; + } } else { - _suggestTheseIssuers(iss_l, account, 0); + _suggestTheseIssuers(iss_l, account, arguments, 0); } - // const char* iss = account_getIssuerUrl(account); - // for (size_t i = 0; i < iss_l->len; i++) { - // if (compIssuerUrls(list_at(iss_l, i)->val, iss)) { - // _askOrNeedScope( - // JSONArrayStringToDelimitedString(list_at(scopes_l, i)->val, " - // "), account, arguments, 0); - // removeScope(account_getScope(account), OIDC_SCOPE_OFFLINE_ACCESS); - // break; - // } - // } - // secFreeList(iss_l); - // secFreeList(scopes_l); + secFreeList(iss_l); readMyProfile(account, arguments); readRefreshToken(account, arguments); return account; } +oidc: needIssuer(account, arguments); needClientId(account, arguments); askOrNeedClientSecret(account, arguments, arguments->usePublicClient); @@ -604,11 +625,7 @@ struct oidc_account* registerClient(struct arguments* arguments) { if (arguments->oauth) { account_setOAuth2(account); } - needName(account, arguments); - if (oidcFileDoesExist(account_getName(account))) { - printError("An account with that shortname is already configured\n"); - exit(EXIT_FAILURE); - } + needName(account, 1, arguments->args[0], arguments->cnid); char* tmpFile = oidc_strcat(CLIENT_TMP_PREFIX, account_getName(account)); char* tmpData = readFileFromAgent(tmpFile, IGNORE_ERROR); @@ -932,6 +949,38 @@ void handleDelete(const struct arguments* arguments) { secFree(json); } +oidc_error_t gen_addAfterStoreForPW_callback(const char* text, + const char* account, + const char* password) { + if (password == NULL) { + return OIDC_SUCCESS; + } + char* iss = getJSONValueFromString(text, OIDC_KEY_ISSUER); + if (iss == NULL) { + iss = getJSONValueFromString(text, AGENT_KEY_ISSUERURL); + } + const struct issuerConfig* iss_c = getIssuerConfig(iss); + secFree(iss); + if (iss_c == NULL || !iss_c->store_pw) { + return OIDC_SUCCESS; + } + struct password_entry pw = {.shortname = (char*)account}; + pwe_setPassword(&pw, (char*)password); + pwe_setType(&pw, PW_TYPE_PRMT | PW_TYPE_MEM); + char* pw_str = passwordEntryToJSONString(&pw); + char* res = + ipc_cryptCommunicate(remote, REQUEST_ADD_LIFETIME, text, 0, pw_str, 0, 0); + secFree(pw_str); + char* error = parseForError(res); + if (error == NULL) { + return OIDC_SUCCESS; + } + oidc_seterror(error); + secFree(error); + oidc_errno = OIDC_EERROR; + return oidc_errno; +} + /** * @brief encrypts and writes an account configuration. * @param config the json encoded account configuration text. Might be @@ -963,7 +1012,8 @@ oidc_error_t gen_saveAccountConfig(const char* config, const char* shortname, } return promptEncryptAndWriteToOidcFile( config, shortname, hint, suggestedPassword, arguments->pw_cmd, - arguments->pw_file, arguments->pw_env, arguments->pw_gpg); + arguments->pw_file, arguments->pw_env, arguments->pw_gpg, + gen_addAfterStoreForPW_callback); } char* text = mergeJSONObjectStrings(config, tmpData); oidc_error_t merge_error = OIDC_SUCCESS; @@ -981,7 +1031,8 @@ oidc_error_t gen_saveAccountConfig(const char* config, const char* shortname, } oidc_error_t e = promptEncryptAndWriteToOidcFile( text, shortname, hint, suggestedPassword, arguments->pw_cmd, - arguments->pw_file, arguments->pw_env, arguments->pw_gpg); + arguments->pw_file, arguments->pw_env, arguments->pw_gpg, + gen_addAfterStoreForPW_callback); secFree(text); if (e == OIDC_SUCCESS && merge_error == OIDC_SUCCESS) { removeFileFromAgent(tmpFile); @@ -1025,14 +1076,15 @@ void gen_handleUpdateConfigFile(const char* file, exit(oidc_errno); } if (isJSONObject(fileContent)) { - oidc_error_t (*writeFnc)(const char*, const char*, const char*, const char*, - const char*, const char*, const char*, - const char*) = + oidc_error_t (*writeFnc)( + const char*, const char*, const char*, const char*, const char*, + const char*, const char*, const char*, + oidc_error_t (*callback)(const char*, const char*, const char*)) = isShortname ? promptEncryptAndWriteToOidcFile : promptEncryptAndWriteToFile; - oidc_error_t write_e = - writeFnc(fileContent, file, file, NULL, arguments->pw_cmd, - arguments->pw_file, arguments->pw_env, arguments->pw_gpg); + oidc_error_t write_e = writeFnc(fileContent, file, file, NULL, + arguments->pw_cmd, arguments->pw_file, + arguments->pw_env, arguments->pw_gpg, NULL); secFree(fileContent); if (write_e != OIDC_SUCCESS) { oidc_perror(); @@ -1096,15 +1148,22 @@ oidc_error_t gen_handlePublicClient(struct oidc_account* account, if (account_getClientId(account) == old_client_id) { return OIDC_ENOPUBCLIENT; } + const list_t* flows = getPubClientFlows(account_getIssuerUrl(account)); + if (flows != NULL && !arguments->flows_set) { + secFreeList(arguments->flows); + arguments->flows = copyList(flows); + } handleGen(account, arguments, NULL); return OIDC_SUCCESS; } char* gen_handleScopeLookup(const struct oidc_account* account) { - const char* iss = account_getIssuerUrl(account); - char* res = ipc_cryptCommunicate(remote, REQUEST_SCOPES, iss, - account_getConfigEndpoint(account), - account_getCertPath(account)); + const char* iss = account_getIssuerUrl(account); + char* cert_path = account_getCertPathOrDefault(account); + char* res = + ipc_cryptCommunicate(remote, REQUEST_SCOPES, iss, + account_getConfigEndpoint(account), cert_path); + secFree(cert_path); INIT_KEY_VALUE(IPC_KEY_STATUS, OIDC_KEY_ERROR, IPC_KEY_INFO); if (CALL_GETJSONVALUES(res) < 0) { @@ -1127,10 +1186,12 @@ char* gen_handleScopeLookup(const struct oidc_account* account) { } char* gen_handleMytokenProvidersLookup(const struct oidc_account* account) { - const char* iss = account_getMytokenUrl(account); - char* res = ipc_cryptCommunicate(remote, REQUEST_MYTOKEN_PROVIDERS, iss, - account_getConfigEndpoint(account), - account_getCertPath(account)); + const char* iss = account_getMytokenUrl(account); + char* cert_path = account_getCertPathOrDefault(account); + char* res = + ipc_cryptCommunicate(remote, REQUEST_MYTOKEN_PROVIDERS, iss, + account_getConfigEndpoint(account), cert_path); + secFree(cert_path); INIT_KEY_VALUE(IPC_KEY_STATUS, OIDC_KEY_ERROR, IPC_KEY_INFO); if (CALL_GETJSONVALUES(res) < 0) { @@ -1217,15 +1278,24 @@ void handleOnlyAT(struct arguments* arguments) { account = secAlloc(sizeof(struct oidc_account)); readConfigEndpoint(account, arguments); needIssuer(account, arguments); - updateAccountWithPublicClientInfo(account); - arguments->usePublicClient = 1; + updateAccountWithUserClientInfo(account); + const list_t* flows = getUserClientFlows(account_getIssuerUrl(account)); + if (account_getClientId(account) == NULL) { + updateAccountWithPublicClientInfo(account); + arguments->usePublicClient = 1; + flows = getPubClientFlows(account_getIssuerUrl(account)); + } + if (flows != NULL && !arguments->flows_set) { + secFreeList(arguments->flows); + arguments->flows = copyList(flows); + } } else if (arguments->file) { account = getAccountFromMaybeEncryptedFile(arguments->file); } if (arguments->oauth) { account_setOAuth2(account); } - account = manual_genNewAccount(account, arguments, NULL); + account = manual_genNewAccount(account, arguments, NULL, 0); char* at = _gen_response(account, arguments); printStdout("%s\n", at); diff --git a/src/oidc-gen/gen_handler.h b/src/oidc-gen/gen_handler.h index a124d2dd..486dedaf 100644 --- a/src/oidc-gen/gen_handler.h +++ b/src/oidc-gen/gen_handler.h @@ -5,13 +5,14 @@ #include "oidc-gen_options.h" #include "utils/oidc_error.h" -void manualGen(struct oidc_account* account, const struct arguments* arguments); +void manualGen(struct oidc_account* account, const struct arguments* arguments, + unsigned char on_mytoken_preferred_but_fails_return); void reauthenticate(const char* shortname, struct arguments* arguments); void handleGen(struct oidc_account* account, const struct arguments* arguments, const char* cryptPass); -struct oidc_account* manual_genNewAccount(struct oidc_account* account, - const struct arguments* arguments, - char** cryptPassPtr); +struct oidc_account* manual_genNewAccount( + struct oidc_account* account, const struct arguments* arguments, + char** cryptPassPtr, unsigned char* on_mytoken_preferred_but_fails_return); struct oidc_account* registerClient(struct arguments* arguments); void handleDelete(const struct arguments*); oidc_error_t gen_saveAccountConfig(const char* config, const char* shortname, diff --git a/src/oidc-gen/oidc-gen.c b/src/oidc-gen/oidc-gen.c index 5be38029..7c7f98a8 100644 --- a/src/oidc-gen/oidc-gen.c +++ b/src/oidc-gen/oidc-gen.c @@ -5,6 +5,7 @@ #include "gen_handler.h" #include "utils/accountUtils.h" #include "utils/commonFeatures.h" +#include "utils/config/gen_config.h" #include "utils/disableTracing.h" #include "utils/file_io/fileUtils.h" #include "utils/listUtils.h" @@ -68,9 +69,12 @@ int main(int argc, char** argv) { if (arguments.file) { account = getAccountFromMaybeEncryptedFile(arguments.file); } - manualGen(account, &arguments); + manualGen(account, &arguments, 0); } else { - struct oidc_account* account = registerClient(&arguments); + if (getGenConfig()->prefer_mytoken_over_oidc) { + manualGen(NULL, &arguments, 1); + } + account = registerClient(&arguments); if (account) { handleGen(account, &arguments, NULL); } else { diff --git a/src/oidc-gen/oidc-gen_options.c b/src/oidc-gen/oidc-gen_options.c index 2b4720f8..0e49b85b 100644 --- a/src/oidc-gen/oidc-gen_options.c +++ b/src/oidc-gen/oidc-gen_options.c @@ -5,12 +5,14 @@ #include "defines/agent_values.h" #include "defines/settings.h" #include "utils/commonFeatures.h" +#include "utils/config/configUtils.h" +#include "utils/config/gen_config.h" #include "utils/guiChecker.h" #include "utils/listUtils.h" #include "utils/memory.h" #include "utils/portUtils.h" #include "utils/printer.h" -#include "utils/prompt_mode.h" +#include "utils/prompting/prompt_mode.h" #include "utils/string/stringUtils.h" #include "utils/uriUtils.h" @@ -140,7 +142,7 @@ static struct argp_option options[] = { {0, 0, 0, 0, "Generating a new account configuration - Advanced:", 3}, {"at", OPT_TOKEN, "ACCESS_TOKEN", 0, - "Use ACCESS_TOKEN for authorization for authorization at the registration " + "Use ACCESS_TOKEN for authorization at the registration " "endpoint.", 3}, {"access-token", OPT_TOKEN, "ACCESS_TOKEN", OPTION_ALIAS, NULL, 3}, @@ -279,7 +281,7 @@ void initArguments(struct arguments* arguments) { arguments->pw_env = NULL; arguments->pw_cmd = NULL; arguments->pw_file = NULL; - arguments->pw_gpg = NULL; + arguments->pw_gpg = getGenConfig()->default_gpg_key; arguments->file = NULL; arguments->client_id = NULL; @@ -290,38 +292,42 @@ void initArguments(struct arguments* arguments) { arguments->dynRegToken = NULL; arguments->cert_path = NULL; arguments->refresh_token = NULL; - arguments->cnid = NULL; + arguments->cnid = getGenConfig()->cnid; arguments->audience = NULL; arguments->op_username = NULL; arguments->op_password = NULL; arguments->mytoken_profile = NULL; - arguments->mytoken_issuer.str = NULL; + arguments->mytoken_issuer.str = getGenConfig()->default_mytoken_server; arguments->mytoken_issuer.useIt = 0; arguments->flows = NULL; + arguments->flows_set = 1; arguments->redirect_uris = NULL; - arguments->debug = 0; + arguments->debug = getGenConfig()->debug; arguments->manual = 0; arguments->verbose = 0; arguments->delete = 0; arguments->listAccounts = 0; - arguments->noUrlCall = 0; + arguments->noUrlCall = !getGenConfig()->autoopenurl; arguments->usePublicClient = 0; arguments->noWebserver = 0; arguments->reauthenticate = 0; arguments->noScheme = 0; - arguments->confirm_no = 0; - arguments->confirm_yes = 0; - arguments->confirm_default = 0; - arguments->only_at = 0; - arguments->noSave = 0; - arguments->oauth = 0; + arguments->confirm_no = + getGenConfig()->answer_confirm_prompts_mode == CONFIRM_PROMPT_MODE_NO; + arguments->confirm_yes = + getGenConfig()->answer_confirm_prompts_mode == CONFIRM_PROMPT_MODE_YES; + arguments->confirm_default = getGenConfig()->answer_confirm_prompts_mode == + CONFIRM_PROMPT_MODE_DEFAULT; + arguments->only_at = 0; + arguments->noSave = 0; + arguments->oauth = 0; - arguments->pw_prompt_mode = 0; + arguments->pw_prompt_mode = getGenConfig()->pw_prompt_mode; set_pw_prompt_mode(arguments->pw_prompt_mode); - arguments->prompt_mode = PROMPT_MODE_CLI; + arguments->prompt_mode = getGenConfig()->prompt_mode; set_prompt_mode(arguments->prompt_mode); } @@ -422,27 +428,23 @@ static error_t parse_opt(int key, char* arg, struct argp_state* state) { arguments->rename = arg; break; case OPT_PW_PROMPT_MODE: - if (strequal(arg, "cli")) { - arguments->pw_prompt_mode = PROMPT_MODE_CLI; - } else if (strequal(arg, "gui")) { - arguments->pw_prompt_mode = PROMPT_MODE_GUI; - common_assertOidcPrompt(); - } else { + arguments->pw_prompt_mode = parse_prompt_mode(arg); + if (arguments->pw_prompt_mode == PROMPT_MODE_INVALID) { return ARGP_ERR_UNKNOWN; } + if (arguments->pw_prompt_mode == PROMPT_MODE_GUI) { + common_assertOidcPrompt(); + } set_pw_prompt_mode(arguments->pw_prompt_mode); break; case OPT_PROMPT_MODE: - if (strequal(arg, "cli")) { - arguments->prompt_mode = PROMPT_MODE_CLI; - } else if (strequal(arg, "gui")) { - arguments->prompt_mode = PROMPT_MODE_GUI; - common_assertOidcPrompt(); - } else if (strequal(arg, "none")) { - arguments->prompt_mode = 0; - } else { + arguments->prompt_mode = parse_prompt_mode(arg); + if (arguments->prompt_mode == PROMPT_MODE_INVALID) { return ARGP_ERR_UNKNOWN; } + if (arguments->prompt_mode == PROMPT_MODE_GUI) { + common_assertOidcPrompt(); + } set_prompt_mode(arguments->prompt_mode); break; case OPT_TOKEN: arguments->dynRegToken = arg; break; @@ -459,10 +461,14 @@ static error_t parse_opt(int key, char* arg, struct argp_state* state) { } case OPT_MYTOKENURL: arguments->mytoken_issuer.useIt = 1; - arguments->mytoken_issuer.str = arg; - arguments->client_id = "mytoken"; - arguments->client_secret = "mytoken"; - arguments->manual = 1; + if (arg != NULL) { + arguments->mytoken_issuer.str = arg; + } + arguments->mytoken_profile = + arguments->mytoken_profile ?: getGenConfig()->default_mytoken_profile; + arguments->client_id = "mytoken"; + arguments->client_secret = "mytoken"; + arguments->manual = 1; break; case OPT_MYTOKENPROFILE: arguments->mytoken_profile = arg; @@ -544,6 +550,7 @@ static error_t parse_opt(int key, char* arg, struct argp_state* state) { _setMTFlow(arguments); } if (arguments->flows == NULL) { + arguments->flows_set = 0; arguments->flows = list_new(); arguments->flows->match = (matchFunction)strequal; if (GUIAvailable()) { diff --git a/src/oidc-gen/oidc-gen_options.h b/src/oidc-gen/oidc-gen_options.h index f6482f7f..97091ccc 100644 --- a/src/oidc-gen/oidc-gen_options.h +++ b/src/oidc-gen/oidc-gen_options.h @@ -58,8 +58,9 @@ struct arguments { struct optional_arg mytoken_issuer; - list_t* flows; - list_t* redirect_uris; + list_t* flows; + unsigned char flows_set; + list_t* redirect_uris; unsigned char delete; unsigned char listAccounts; diff --git a/src/oidc-gen/promptAndSet/_helper.c b/src/oidc-gen/promptAndSet/_helper.c index 4eddc455..b660aac8 100644 --- a/src/oidc-gen/promptAndSet/_helper.c +++ b/src/oidc-gen/promptAndSet/_helper.c @@ -3,7 +3,8 @@ #include "utils/listUtils.h" #include "utils/logger.h" #include "utils/memory.h" -#include "utils/prompt.h" +#include "utils/prompting/getprompt.h" +#include "utils/prompting/prompt.h" #include "utils/string/stringUtils.h" char* _gen_prompt(char* label, const char* init, int passPrompt, int optional) { @@ -13,7 +14,7 @@ char* _gen_prompt(char* label, const char* init, int passPrompt, int optional) { prompter = promptPassword; } do { - char* text = oidc_sprintf("Please enter %s:", label); + char* text = get_general_prompt(label); input = prompter(text, label, init, CLI_PROMPT_NOT_VERBOSE); secFree(text); if (strValid(input)) { @@ -31,7 +32,7 @@ char* _gen_promptMultipleSpaceSeparated(char* label, const char* init_str, list_t* init = delimitedStringToList(init_str, ' '); list_t* input = NULL; do { - char* text = oidc_sprintf("Please enter %s:", label); + char* text = get_general_prompt(label); input = promptMultiple(text, label, init, CLI_PROMPT_NOT_VERBOSE); secFree(text); if (listValid(input)) { diff --git a/src/oidc-gen/promptAndSet/_helper.h b/src/oidc-gen/promptAndSet/_helper.h index bfaf1f1d..a04ddc09 100644 --- a/src/oidc-gen/promptAndSet/_helper.h +++ b/src/oidc-gen/promptAndSet/_helper.h @@ -4,7 +4,7 @@ #include #include "utils/printer.h" -#include "utils/prompt_mode.h" +#include "utils/prompting/prompt_mode.h" #ifndef ERROR_MESSAGE #define ERROR_MESSAGE(_name, _option) \ diff --git a/src/oidc-gen/promptAndSet/certpath.c b/src/oidc-gen/promptAndSet/certpath.c index f0355265..7c936f29 100644 --- a/src/oidc-gen/promptAndSet/certpath.c +++ b/src/oidc-gen/promptAndSet/certpath.c @@ -28,9 +28,6 @@ int readCertPath(struct oidc_account* account, if (prompt_mode() == 0 && strValid(account_getCertPath(account))) { return 1; } - if (account_getCertPath(account) == NULL) { - account_setOSDefaultCertPath(account); - } return 0; } diff --git a/src/oidc-gen/promptAndSet/clientid.c b/src/oidc-gen/promptAndSet/clientid.c index 12398170..bcdbf011 100644 --- a/src/oidc-gen/promptAndSet/clientid.c +++ b/src/oidc-gen/promptAndSet/clientid.c @@ -10,7 +10,7 @@ void askOrNeedClientId(struct oidc_account* account, } ERROR_IF_NO_PROMPT(optional, ERROR_MESSAGE("client id", OPT_LONG_CLIENTID)); char* cid = - _gen_prompt("Client_id", account_getClientId(account), 0, optional); + _gen_prompt("Client id", account_getClientId(account), 0, optional); if (cid) { account_setClientId(account, cid); } diff --git a/src/oidc-gen/promptAndSet/clientsecret.c b/src/oidc-gen/promptAndSet/clientsecret.c index 7ed18ef2..83046acf 100644 --- a/src/oidc-gen/promptAndSet/clientsecret.c +++ b/src/oidc-gen/promptAndSet/clientsecret.c @@ -10,7 +10,7 @@ void askOrNeedClientSecret(struct oidc_account* account, } ERROR_IF_NO_PROMPT(optional, ERROR_MESSAGE("client secret", OPT_LONG_CLIENTSECRET)); - char* res = _gen_prompt("Client_secret", account_getClientSecret(account), 1, + char* res = _gen_prompt("Client secret", account_getClientSecret(account), 1, optional); if (res) { account_setClientSecret(account, res); diff --git a/src/oidc-gen/promptAndSet/issuer.c b/src/oidc-gen/promptAndSet/issuer.c index 3b7472d6..e543a31e 100644 --- a/src/oidc-gen/promptAndSet/issuer.c +++ b/src/oidc-gen/promptAndSet/issuer.c @@ -2,25 +2,50 @@ #include "account/account.h" #include "account/issuer_helper.h" #include "defines/msys.h" -#include "defines/settings.h" #include "promptAndSet.h" -#include "utils/file_io/file_io.h" -#include "utils/file_io/oidc_file_io.h" +#include "utils/config/issuerConfig.h" #include "utils/listUtils.h" -#include "utils/prompt.h" +#include "utils/prompting/getprompt.h" +#include "utils/prompting/prompt.h" #include "utils/string/stringUtils.h" -void _useSuggestedIssuer(struct oidc_account* account, int optional) { - list_t* issuers = getSuggestableIssuers(); - _suggestTheseIssuers(issuers, account, optional); - secFreeList(issuers); +static void applyIssuerDefaultConfig(struct oidc_account* account, + const struct arguments* arguments) { + const struct issuerConfig* iss = + getIssuerConfig(account_getIssuerUrl(account)); + if (iss == NULL) { + return; + } + if (arguments->configuration_endpoint == NULL && + iss->configuration_endpoint != NULL) { + account_setConfigEndpoint(account, + oidc_strcopy(iss->configuration_endpoint)); + } + if (arguments->device_authorization_endpoint == NULL && + iss->device_authorization_endpoint != NULL) { + issuer_setDeviceAuthorizationEndpoint( + account_getIssuer(account), + oidc_strcopy(iss->device_authorization_endpoint), 1); + } + if (arguments->cert_path == NULL && iss->cert_path != NULL) { + account_setCertPath(account, oidc_strcopy(iss->cert_path)); + } + if (!arguments->oauth && iss->oauth) { + account_setOAuth2(account); + } + if (!arguments->usePublicClient && !arguments->client_id && + iss->user_client) { + updateAccountWithUserClientInfo(account); + } } void _suggestTheseIssuers(list_t* issuers, struct oidc_account* account, - int optional) { - size_t favPos = getFavIssuer(account, issuers); - char* iss = promptSelect("Please select issuer", "Issuer", issuers, favPos, - CLI_PROMPT_NOT_VERBOSE); + const struct arguments* arguments, int optional) { + size_t favPos = getFavIssuer(account, issuers); + char* prompt_text = get_general_select_prompt("Issuer"); + char* iss = promptSelect(prompt_text, "Issuer", issuers, favPos, + CLI_PROMPT_NOT_VERBOSE); + secFree(prompt_text); if (!strValid(iss)) { printError("Something went wrong. Invalid Issuer.\n"); if (optional) { @@ -29,6 +54,7 @@ void _suggestTheseIssuers(list_t* issuers, struct oidc_account* account, exit(EXIT_FAILURE); } account_setIssuerUrl(account, iss); + applyIssuerDefaultConfig(account, arguments); } void askOrNeedIssuer(struct oidc_account* account, @@ -38,27 +64,25 @@ void askOrNeedIssuer(struct oidc_account* account, return; } ERROR_IF_NO_PROMPT(optional, ERROR_MESSAGE("issuer url", OPT_LONG_ISSUER)); - if (!oidcFileDoesExist(ISSUER_CONFIG_FILENAME) && !fileDoesExist( -#ifdef ANY_MSYS - ETC_ISSUER_CONFIG_FILE() -#else - ETC_ISSUER_CONFIG_FILE -#endif - )) { + list_t* issuers = getSuggestableIssuers(); + if (listValid(issuers)) { + _suggestTheseIssuers(issuers, account, arguments, optional); + } else { char* res = _gen_prompt("Issuer", account_getIssuerUrl(account), 0, optional); if (res) { account_setIssuerUrl(account, res); + applyIssuerDefaultConfig(account, arguments); } - } else { - _useSuggestedIssuer(account, optional); } + secFreeList(issuers); } int readIssuer(struct oidc_account* account, const struct arguments* arguments) { if (arguments->issuer) { account_setIssuerUrl(account, oidc_strcopy(arguments->issuer)); + applyIssuerDefaultConfig(account, arguments); return 1; } if (prompt_mode() == 0 && strValid(account_getIssuerUrl(account))) { diff --git a/src/oidc-gen/promptAndSet/mytokenProfile.c b/src/oidc-gen/promptAndSet/mytokenProfile.c index 84f78ef0..6b010058 100644 --- a/src/oidc-gen/promptAndSet/mytokenProfile.c +++ b/src/oidc-gen/promptAndSet/mytokenProfile.c @@ -1,6 +1,7 @@ #include "_helper.h" #include "account/account.h" #include "promptAndSet.h" +#include "utils/config/gen_config.h" #include "utils/json.h" #include "utils/string/stringUtils.h" @@ -43,6 +44,10 @@ int readMyProfile(struct oidc_account* account, return parseAndSetProfile(account, oidc_strcopy(arguments->mytoken_profile)); } + if (getGenConfig()->default_mytoken_profile) { + return parseAndSetProfile( + account, oidc_strcopy(getGenConfig()->default_mytoken_profile)); + } if (prompt_mode() == 0 && strValid(account_getUsedMytokenProfile(account))) { return 1; } diff --git a/src/oidc-gen/promptAndSet/mytoken_issuer.c b/src/oidc-gen/promptAndSet/mytoken_issuer.c index 67fe71bc..f8e2b658 100644 --- a/src/oidc-gen/promptAndSet/mytoken_issuer.c +++ b/src/oidc-gen/promptAndSet/mytoken_issuer.c @@ -2,7 +2,6 @@ #include "account/account.h" #include "promptAndSet.h" #include "utils/listUtils.h" -#include "utils/prompt.h" #include "utils/string/stringUtils.h" void askOrNeedMytokenIssuer(struct oidc_account* account, diff --git a/src/oidc-gen/promptAndSet/name.c b/src/oidc-gen/promptAndSet/name.c index 4bdb3a3e..0cf4dce3 100644 --- a/src/oidc-gen/promptAndSet/name.c +++ b/src/oidc-gen/promptAndSet/name.c @@ -1,29 +1,50 @@ #include "_helper.h" #include "account/account.h" #include "promptAndSet.h" -#include "utils/prompt.h" +#include "utils/file_io/oidc_file_io.h" +#include "utils/json.h" +#include "utils/prompting/getprompt.h" +#include "utils/prompting/prompt.h" #include "utils/string/stringUtils.h" -void askOrNeedName(struct oidc_account* account, - const struct arguments* arguments, int optional) { - if (readName(account, arguments)) { - return; +void askOrNeedName(struct oidc_account* account, const char* arg0, + const char* cnid, unsigned char optional, + unsigned char shouldNotExist, const char* suggestion) { + if (readName(account, arg0, cnid)) { + if (!shouldNotExist || !oidcFileDoesExist(account_getName(account))) { + return; + } } ERROR_IF_NO_PROMPT(optional, "No account short name given."); - char* shortname = NULL; + char* shortname = NULL; + unsigned char exists = strValid(account_getName(account)) + ? oidcFileDoesExist(account_getName(account)) + : 0; + cJSON* data = cJSON_CreateObject(); + if (exists) { + data = jsonAddBoolValue(data, "exists", cJSON_True); + } do { secFree(shortname); - shortname = prompt("Enter short name for the account to configure", - "short name", NULL, CLI_PROMPT_VERBOSE); - } while (!strValid(shortname) || optional); + char* text = getprompt(PROMPTTEMPLATE(SHORTNAME), data); + shortname = prompt(text, "short name", suggestion, CLI_PROMPT_VERBOSE); + secFree(text); + secFreeJson(data); + exists = strValid(shortname) ? oidcFileDoesExist(shortname) : 0; + data = cJSON_CreateObject(); + if (exists) { + data = jsonAddBoolValue(data, "exists", cJSON_True); + } + } while ((!strValid(shortname) && !optional) || (exists && shouldNotExist)); + secFreeJson(data); if (shortname) { - account_setName(account, shortname, arguments->cnid); + account_setName(account, shortname, cnid); } } -int readName(struct oidc_account* account, const struct arguments* arguments) { - if (arguments->args[0]) { - account_setName(account, oidc_strcopy(arguments->args[0]), arguments->cnid); +int readName(struct oidc_account* account, const char* arg0, const char* cnid) { + if (arg0) { + account_setName(account, oidc_strcopy(arg0), cnid); return 1; } if (prompt_mode() == 0 && strValid(account_getName(account))) { @@ -32,10 +53,12 @@ int readName(struct oidc_account* account, const struct arguments* arguments) { return 0; } -void askName(struct oidc_account* account, const struct arguments* arguments) { - return askOrNeedName(account, arguments, 1); +void askName(struct oidc_account* account, unsigned char shouldNotExist, + const char* arg0, const char* cnid) { + return askOrNeedName(account, arg0, cnid, 1, shouldNotExist, NULL); } -void needName(struct oidc_account* account, const struct arguments* arguments) { - return askOrNeedName(account, arguments, 0); +void needName(struct oidc_account* account, unsigned char shouldNotExist, + const char* arg0, const char* cnid) { + return askOrNeedName(account, arg0, cnid, 0, shouldNotExist, NULL); } diff --git a/src/oidc-gen/promptAndSet/name.h b/src/oidc-gen/promptAndSet/name.h new file mode 100644 index 00000000..b97b9a7d --- /dev/null +++ b/src/oidc-gen/promptAndSet/name.h @@ -0,0 +1,12 @@ +#ifndef OIDCGEN_PROMPTANDSET_NAME_H +#define OIDCGEN_PROMPTANDSET_NAME_H + +#include "account/account.h" + +int readName(struct oidc_account*, const char*, const char*); +void askName(struct oidc_account*, unsigned char, const char*, const char*); +void needName(struct oidc_account*, unsigned char, const char*, const char*); +void askOrNeedName(struct oidc_account*, const char*, const char*, + unsigned char, unsigned char, const char*); + +#endif // OIDCGEN_PROMPTANDSET_NAME_H diff --git a/src/oidc-gen/promptAndSet/promptAndSet.h b/src/oidc-gen/promptAndSet/promptAndSet.h index 6e3b8eb6..a16d3afa 100644 --- a/src/oidc-gen/promptAndSet/promptAndSet.h +++ b/src/oidc-gen/promptAndSet/promptAndSet.h @@ -2,6 +2,7 @@ #define OIDCGEN_PROMPTANDSET_H #include "account/account.h" +#include "name.h" #include "oidc-gen/oidc-gen_options.h" int readClientId(struct oidc_account*, const struct arguments*); @@ -15,7 +16,7 @@ void needClientSecret(struct oidc_account*, const struct arguments*); void askOrNeedClientSecret(struct oidc_account*, const struct arguments*, int); void _suggestTheseIssuers(list_t* issuers, struct oidc_account* account, - int optional); + const struct arguments* arguments, int optional); int readIssuer(struct oidc_account*, const struct arguments*); void askIssuer(struct oidc_account*, const struct arguments*); void needIssuer(struct oidc_account*, const struct arguments*); @@ -56,11 +57,6 @@ void askCertPath(struct oidc_account*, const struct arguments*); void needCertPath(struct oidc_account*, const struct arguments*); void askOrNeedCertPath(struct oidc_account*, const struct arguments*, int); -int readName(struct oidc_account*, const struct arguments*); -void askName(struct oidc_account*, const struct arguments*); -void needName(struct oidc_account*, const struct arguments*); -void askOrNeedName(struct oidc_account*, const struct arguments*, int); - void _askOrNeedScope(char* supportedScope, struct oidc_account* account, const struct arguments* arguments, int optional); int readScope(struct oidc_account*, const struct arguments*); diff --git a/src/oidc-gen/promptAndSet/redirecturis.c b/src/oidc-gen/promptAndSet/redirecturis.c index 8ee731d4..bebcfa24 100644 --- a/src/oidc-gen/promptAndSet/redirecturis.c +++ b/src/oidc-gen/promptAndSet/redirecturis.c @@ -2,7 +2,8 @@ #include "account/account.h" #include "promptAndSet.h" #include "utils/listUtils.h" -#include "utils/prompt.h" +#include "utils/prompting/getprompt.h" +#include "utils/prompting/prompt.h" #include "utils/uriUtils.h" void askOrNeedRedirectUris(struct oidc_account* account, @@ -13,9 +14,11 @@ void askOrNeedRedirectUris(struct oidc_account* account, ERROR_IF_NO_PROMPT(optional, ERROR_MESSAGE("redirect uri", OPT_LONG_REDIRECT)); while (1) { - list_t* redirect_uris = promptMultiple( - "Please enter Redirect URIs", "Redirect_uris", - account_getRedirectUris(account), CLI_PROMPT_NOT_VERBOSE); + char* prompt_text = get_general_prompt("Redirect URIs"); + list_t* redirect_uris = promptMultiple(prompt_text, "Redirect_uris", + account_getRedirectUris(account), + CLI_PROMPT_NOT_VERBOSE); + secFree(prompt_text); oidc_error_t err = checkRedirectUrisForErrors(redirect_uris); if (err == OIDC_SUCCESS) { account_setRedirectUris(account, redirect_uris); diff --git a/src/oidc-gen/promptAndSet/scope.c b/src/oidc-gen/promptAndSet/scope.c index c4f55761..85bd765c 100644 --- a/src/oidc-gen/promptAndSet/scope.c +++ b/src/oidc-gen/promptAndSet/scope.c @@ -6,10 +6,20 @@ #include "defines/settings.h" #include "oidc-gen/gen_handler.h" #include "promptAndSet.h" +#include "utils/config/issuerConfig.h" #include "utils/string/stringUtils.h" char* getSupportedScopes(struct oidc_account* account, const struct arguments* arguments) { + const struct issuerConfig* issC = + getIssuerConfig(account_getIssuerUrl(account)); + if (issC && issC->user_client && + strequal(account_getClientId(account), issC->user_client->client_id)) { + const char* clientScopes = issC->user_client->scope; + if (strValid(clientScopes)) { + return oidc_strcopy(clientScopes); + } + } if (arguments->usePublicClient) { char* pubScopes = getScopesForPublicClient(account); if (strValid(pubScopes)) { diff --git a/src/oidc-prompt/html/gen.sh b/src/oidc-prompt/html/gen.sh index 85b76d2b..d3a3c56b 100755 --- a/src/oidc-prompt/html/gen.sh +++ b/src/oidc-prompt/html/gen.sh @@ -1,3 +1,4 @@ +#!/bin/bash truncate -s 0 templates.h truncate -s 0 templates.c diff --git a/src/oidc-prompt/html/partials/text.mustache b/src/oidc-prompt/html/partials/text.mustache index a90816d3..05a44390 100644 --- a/src/oidc-prompt/html/partials/text.mustache +++ b/src/oidc-prompt/html/partials/text.mustache @@ -1 +1 @@ -

{{{text}}}

\ No newline at end of file +{{{text}}} diff --git a/src/oidc-prompt/html/sites/link.mustache b/src/oidc-prompt/html/sites/link.mustache index 69e98401..c85a2e23 100644 --- a/src/oidc-prompt/html/sites/link.mustache +++ b/src/oidc-prompt/html/sites/link.mustache @@ -1,10 +1,12 @@ {{> partials/text}} +{{#label}}

- {{label}} + {{.}}

- +
+{{/label}} {{> partials/imgdata}} diff --git a/src/oidc-prompt/html/static/css/custom.css b/src/oidc-prompt/html/static/css/custom.css index 5d4fac84..4fba1719 100644 --- a/src/oidc-prompt/html/static/css/custom.css +++ b/src/oidc-prompt/html/static/css/custom.css @@ -21,3 +21,8 @@ button { .d-none { display: none; } + +.tiny { + font-size-adjust: 0.3 +} + diff --git a/src/oidc-prompt/html/templates.h b/src/oidc-prompt/html/templates.h index 565ebb20..eb14502c 100644 --- a/src/oidc-prompt/html/templates.h +++ b/src/oidc-prompt/html/templates.h @@ -9,13 +9,13 @@ #define PART_IMGDATA "{{#img-data}}
{{/img-data}}" #define PART_JS "" #define PART_MYTOKEN_CONSENT "" -#define PART_TEXT "

{{{text}}}

" +#define PART_TEXT "{{{text}}}" #define PART_TIMEOUT "{{#timeout}}{{/timeout}}" -#define PART_CSS "::backdrop,:root{--sans-font:-apple-system,BlinkMacSystemFont,\"Avenir Next\",Avenir,\"Nimbus Sans L\",Roboto,\"Noto Sans\",\"Segoe UI\",Arial,Helvetica,\"Helvetica Neue\",sans-serif;--mono-font:Consolas,Menlo,Monaco,\"Andale Mono\",\"Ubuntu Mono\",monospace;--standard-border-radius:5px;--bg:#fff;--accent-bg:#f5f7ff;--text:#212121;--text-light:#585858;--border:#898EA4;--accent:#0d47a1;--code:#d81b60;--preformatted:#444;--marked:#ffdd33;--disabled:#efefef}@media (prefers-color-scheme:dark){::backdrop,:root{color-scheme:dark;--bg:#212121;--accent-bg:#2b2b2b;--text:#dcdcdc;--text-light:#ababab;--accent:#ffb300;--code:#f06292;--preformatted:#ccc;--disabled:#111}img,video{opacity:.8}}*,::after,::before{box-sizing:border-box}input,progress,select,textarea{appearance:none;-webkit-appearance:none;-moz-appearance:none}html{font-family:var(--sans-font);scroll-behavior:smooth}body{color:var(--text);background-color:var(--bg);font-size:1.15rem;line-height:1.5;display:grid;grid-template-columns:1fr min(45rem,90%) 1fr;margin:0}body>*{grid-column:2}body>header{background-color:var(--accent-bg);border-bottom:1px solid var(--border);text-align:center;padding:0 .5rem 2rem .5rem;grid-column:1/-1}body>header h1{max-width:1200px;margin:1rem auto}body>header p{max-width:40rem;margin:1rem auto}main{padding-top:1.5rem}body>footer{margin-top:4rem;padding:2rem 1rem 1.5rem 1rem;color:var(--text-light);font-size:.9rem;text-align:center;border-top:1px solid var(--border)}h1{font-size:3rem}h2{font-size:2.6rem;margin-top:3rem}h3{font-size:2rem;margin-top:3rem}h4{font-size:1.44rem}h5{font-size:1.15rem}h6{font-size:.96rem}h1,h2,h3,h4,h5,h6,p{overflow-wrap:break-word}h1,h2,h3{line-height:1.1}@media only screen and (max-width:720px){h1{font-size:2.5rem}h2{font-size:2.1rem}h3{font-size:1.75rem}h4{font-size:1.25rem}}a,a:visited{color:var(--accent)}a:hover{text-decoration:none}[role=button],button,input[type=button],input[type=reset],input[type=submit],label[type=button]{border:none;border-radius:var(--standard-border-radius);background-color:var(--accent);font-size:1rem;color:var(--bg);padding:.7rem .9rem;margin:.5rem 0}[role=button][aria-disabled=true],button[disabled],input[type=button][disabled],input[type=checkbox][disabled],input[type=radio][disabled],input[type=reset][disabled],input[type=submit][disabled],select[disabled]{cursor:not-allowed}button[disabled],input:disabled,select:disabled,textarea:disabled{cursor:not-allowed;background-color:var(--disabled);color:var(--text-light)}input[type=range]{padding:0}abbr[title]{cursor:help;text-decoration-line:underline;text-decoration-style:dotted}[role=button]:not([aria-disabled=true]):hover,button:enabled:hover,input[type=button]:enabled:hover,input[type=reset]:enabled:hover,input[type=submit]:enabled:hover,label[type=button]:hover{filter:brightness(1.4);cursor:pointer}button:focus-visible:where(:enabled,[role=button]:not([aria-disabled=true])),input:enabled:focus-visible:where([type=submit],[type=reset],[type=button]){outline:2px solid var(--accent);outline-offset:1px}header>nav{font-size:1rem;line-height:2;padding:1rem 0 0 0}header>nav ol,header>nav ul{align-content:space-around;align-items:center;display:flex;flex-direction:row;flex-wrap:wrap;justify-content:center;list-style-type:none;margin:0;padding:0}header>nav ol li,header>nav ul li{display:inline-block}header>nav a,header>nav a:visited{margin:0 .5rem 1rem .5rem;border:1px solid var(--border);border-radius:var(--standard-border-radius);color:var(--text);display:inline-block;padding:.1rem 1rem;text-decoration:none}header>nav a:hover{border-color:var(--accent);color:var(--accent);cursor:pointer}@media only screen and (max-width:720px){header>nav a{border:none;padding:0;text-decoration:underline;line-height:1}}aside,details,pre,progress{background-color:var(--accent-bg);border:1px solid var(--border);border-radius:var(--standard-border-radius);margin-bottom:1rem}aside{font-size:1rem;width:30%;padding:0 15px;margin-left:15px;float:right}@media only screen and (max-width:720px){aside{width:100%;float:none;margin-left:0}}article,dialog,fieldset{border:1px solid var(--border);padding:1rem;border-radius:var(--standard-border-radius);margin-bottom:1rem}article h2:first-child,section h2:first-child{margin-top:1rem}section{border-top:1px solid var(--border);border-bottom:1px solid var(--border);padding:2rem 1rem;margin:3rem 0}section+section,section:first-child{border-top:0;padding-top:0}section:last-child{border-bottom:0;padding-bottom:0}details{padding:.7rem 1rem}summary{cursor:pointer;font-weight:700;padding:.7rem 1rem;margin:-.7rem -1rem;word-break:break-all}details[open]>summary+*{margin-top:0}details[open]>summary{margin-bottom:.5rem}details[open]>:last-child{margin-bottom:0}table{border-collapse:collapse;margin:1.5rem 0}td,th{border:1px solid var(--border);text-align:left;padding:.5rem}th{background-color:var(--accent-bg);font-weight:700}tr:nth-child(even){background-color:var(--accent-bg)}table caption{font-weight:700;margin-bottom:.5rem}input,select,textarea{font-size:inherit;font-family:inherit;padding:.5rem;margin-bottom:.5rem;color:var(--text);background-color:var(--bg);border:1px solid var(--border);border-radius:var(--standard-border-radius);box-shadow:none;max-width:100%;display:inline-block}label{display:block}textarea:not([cols]){width:100%}select:not([multiple]){background-image:linear-gradient(45deg,transparent 49%,var(--text) 51%),linear-gradient(135deg,var(--text) 51%,transparent 49%);background-position:calc(100% - 15px),calc(100% - 10px);background-size:5px 5px,5px 5px;background-repeat:no-repeat;padding-right:25px}input[type=checkbox],input[type=radio]{vertical-align:middle;position:relative;width:min-content}input[type=checkbox]+label,input[type=radio]+label{display:inline-block}input[type=radio]{border-radius:100%}input[type=checkbox]:checked,input[type=radio]:checked{background-color:var(--accent)}input[type=checkbox]:checked::after{content:\" \";width:.18em;height:.32em;border-radius:0;position:absolute;top:.05em;left:.17em;background-color:transparent;border-right:solid var(--bg) .08em;border-bottom:solid var(--bg) .08em;font-size:1.8em;transform:rotate(45deg)}input[type=radio]:checked::after{content:\" \";width:.25em;height:.25em;border-radius:100%;position:absolute;top:.125em;background-color:var(--bg);left:.125em;font-size:32px}@media only screen and (max-width:720px){input,select,textarea{width:100%}}input[type=color]{height:2.5rem;padding:.2rem}input[type=file]{border:0}hr{border:none;height:1px;background:var(--border);margin:1rem auto}mark{padding:2px 5px;border-radius:var(--standard-border-radius);background-color:var(--marked);color:#000}img,video{max-width:100%;height:auto;border-radius:var(--standard-border-radius)}figure{margin:0;display:block;overflow-x:auto}figcaption{text-align:center;font-size:.9rem;color:var(--text-light);margin-bottom:1rem}blockquote{margin:2rem 0 2rem 2rem;padding:.4rem .8rem;border-left:.35rem solid var(--accent);color:var(--text-light);font-style:italic}cite{font-size:.9rem;color:var(--text-light);font-style:normal}dt{color:var(--text-light)}code,kbd,pre,pre span,samp{font-family:var(--mono-font);color:var(--code)}kbd{color:var(--preformatted);border:1px solid var(--preformatted);border-bottom:3px solid var(--preformatted);border-radius:var(--standard-border-radius);padding:.1rem .4rem}pre{padding:1rem 1.4rem;max-width:100%;overflow:auto;color:var(--preformatted)}pre code{color:var(--preformatted);background:0 0;margin:0;padding:0}progress{width:100%}progress:indeterminate{background-color:var(--accent-bg)}progress::-webkit-progress-bar{border-radius:var(--standard-border-radius);background-color:var(--accent-bg)}progress::-webkit-progress-value{border-radius:var(--standard-border-radius);background-color:var(--accent)}progress::-moz-progress-bar{border-radius:var(--standard-border-radius);background-color:var(--accent);transition-property:width;transition-duration:.3s}progress:indeterminate::-moz-progress-bar{background-color:var(--accent-bg)}dialog{max-width:40rem;margin:auto}dialog::backdrop{background-color:var(--bg);opacity:.8}@media only screen and (max-width:720px){dialog{max-width:100%;margin:auto 1em}}.button,.button:visited{display:inline-block;text-decoration:none;border:none;border-radius:5px;background:var(--accent);font-size:1rem;color:var(--bg);padding:.7rem .9rem;margin:.5rem 0}.button:focus,.button:hover{filter:brightness(1.4);cursor:pointer}.notice{background:var(--accent-bg);border:2px solid var(--border);border-radius:5px;padding:1.5rem;margin:2rem 0}:root {--accent: #242933;}button {margin: 5px;}.center {text-align: center;}.bg-green {background-color: darkgreen;}.bg-red {background-color: darkred;}.d-none {display: none;}" +#define PART_CSS "::backdrop,:root{--sans-font:-apple-system,BlinkMacSystemFont,\"Avenir Next\",Avenir,\"Nimbus Sans L\",Roboto,\"Noto Sans\",\"Segoe UI\",Arial,Helvetica,\"Helvetica Neue\",sans-serif;--mono-font:Consolas,Menlo,Monaco,\"Andale Mono\",\"Ubuntu Mono\",monospace;--standard-border-radius:5px;--bg:#fff;--accent-bg:#f5f7ff;--text:#212121;--text-light:#585858;--border:#898EA4;--accent:#0d47a1;--code:#d81b60;--preformatted:#444;--marked:#ffdd33;--disabled:#efefef}@media (prefers-color-scheme:dark){::backdrop,:root{color-scheme:dark;--bg:#212121;--accent-bg:#2b2b2b;--text:#dcdcdc;--text-light:#ababab;--accent:#ffb300;--code:#f06292;--preformatted:#ccc;--disabled:#111}img,video{opacity:.8}}*,::after,::before{box-sizing:border-box}input,progress,select,textarea{appearance:none;-webkit-appearance:none;-moz-appearance:none}html{font-family:var(--sans-font);scroll-behavior:smooth}body{color:var(--text);background-color:var(--bg);font-size:1.15rem;line-height:1.5;display:grid;grid-template-columns:1fr min(45rem,90%) 1fr;margin:0}body>*{grid-column:2}body>header{background-color:var(--accent-bg);border-bottom:1px solid var(--border);text-align:center;padding:0 .5rem 2rem .5rem;grid-column:1/-1}body>header h1{max-width:1200px;margin:1rem auto}body>header p{max-width:40rem;margin:1rem auto}main{padding-top:1.5rem}body>footer{margin-top:4rem;padding:2rem 1rem 1.5rem 1rem;color:var(--text-light);font-size:.9rem;text-align:center;border-top:1px solid var(--border)}h1{font-size:3rem}h2{font-size:2.6rem;margin-top:3rem}h3{font-size:2rem;margin-top:3rem}h4{font-size:1.44rem}h5{font-size:1.15rem}h6{font-size:.96rem}h1,h2,h3,h4,h5,h6,p{overflow-wrap:break-word}h1,h2,h3{line-height:1.1}@media only screen and (max-width:720px){h1{font-size:2.5rem}h2{font-size:2.1rem}h3{font-size:1.75rem}h4{font-size:1.25rem}}a,a:visited{color:var(--accent)}a:hover{text-decoration:none}[role=button],button,input[type=button],input[type=reset],input[type=submit],label[type=button]{border:none;border-radius:var(--standard-border-radius);background-color:var(--accent);font-size:1rem;color:var(--bg);padding:.7rem .9rem;margin:.5rem 0}[role=button][aria-disabled=true],button[disabled],input[type=button][disabled],input[type=checkbox][disabled],input[type=radio][disabled],input[type=reset][disabled],input[type=submit][disabled],select[disabled]{cursor:not-allowed}button[disabled],input:disabled,select:disabled,textarea:disabled{cursor:not-allowed;background-color:var(--disabled);color:var(--text-light)}input[type=range]{padding:0}abbr[title]{cursor:help;text-decoration-line:underline;text-decoration-style:dotted}[role=button]:not([aria-disabled=true]):hover,button:enabled:hover,input[type=button]:enabled:hover,input[type=reset]:enabled:hover,input[type=submit]:enabled:hover,label[type=button]:hover{filter:brightness(1.4);cursor:pointer}button:focus-visible:where(:enabled,[role=button]:not([aria-disabled=true])),input:enabled:focus-visible:where([type=submit],[type=reset],[type=button]){outline:2px solid var(--accent);outline-offset:1px}header>nav{font-size:1rem;line-height:2;padding:1rem 0 0 0}header>nav ol,header>nav ul{align-content:space-around;align-items:center;display:flex;flex-direction:row;flex-wrap:wrap;justify-content:center;list-style-type:none;margin:0;padding:0}header>nav ol li,header>nav ul li{display:inline-block}header>nav a,header>nav a:visited{margin:0 .5rem 1rem .5rem;border:1px solid var(--border);border-radius:var(--standard-border-radius);color:var(--text);display:inline-block;padding:.1rem 1rem;text-decoration:none}header>nav a:hover{border-color:var(--accent);color:var(--accent);cursor:pointer}@media only screen and (max-width:720px){header>nav a{border:none;padding:0;text-decoration:underline;line-height:1}}aside,details,pre,progress{background-color:var(--accent-bg);border:1px solid var(--border);border-radius:var(--standard-border-radius);margin-bottom:1rem}aside{font-size:1rem;width:30%;padding:0 15px;margin-left:15px;float:right}@media only screen and (max-width:720px){aside{width:100%;float:none;margin-left:0}}article,dialog,fieldset{border:1px solid var(--border);padding:1rem;border-radius:var(--standard-border-radius);margin-bottom:1rem}article h2:first-child,section h2:first-child{margin-top:1rem}section{border-top:1px solid var(--border);border-bottom:1px solid var(--border);padding:2rem 1rem;margin:3rem 0}section+section,section:first-child{border-top:0;padding-top:0}section:last-child{border-bottom:0;padding-bottom:0}details{padding:.7rem 1rem}summary{cursor:pointer;font-weight:700;padding:.7rem 1rem;margin:-.7rem -1rem;word-break:break-all}details[open]>summary+*{margin-top:0}details[open]>summary{margin-bottom:.5rem}details[open]>:last-child{margin-bottom:0}table{border-collapse:collapse;margin:1.5rem 0}td,th{border:1px solid var(--border);text-align:left;padding:.5rem}th{background-color:var(--accent-bg);font-weight:700}tr:nth-child(even){background-color:var(--accent-bg)}table caption{font-weight:700;margin-bottom:.5rem}input,select,textarea{font-size:inherit;font-family:inherit;padding:.5rem;margin-bottom:.5rem;color:var(--text);background-color:var(--bg);border:1px solid var(--border);border-radius:var(--standard-border-radius);box-shadow:none;max-width:100%;display:inline-block}label{display:block}textarea:not([cols]){width:100%}select:not([multiple]){background-image:linear-gradient(45deg,transparent 49%,var(--text) 51%),linear-gradient(135deg,var(--text) 51%,transparent 49%);background-position:calc(100% - 15px),calc(100% - 10px);background-size:5px 5px,5px 5px;background-repeat:no-repeat;padding-right:25px}input[type=checkbox],input[type=radio]{vertical-align:middle;position:relative;width:min-content}input[type=checkbox]+label,input[type=radio]+label{display:inline-block}input[type=radio]{border-radius:100%}input[type=checkbox]:checked,input[type=radio]:checked{background-color:var(--accent)}input[type=checkbox]:checked::after{content:\" \";width:.18em;height:.32em;border-radius:0;position:absolute;top:.05em;left:.17em;background-color:transparent;border-right:solid var(--bg) .08em;border-bottom:solid var(--bg) .08em;font-size:1.8em;transform:rotate(45deg)}input[type=radio]:checked::after{content:\" \";width:.25em;height:.25em;border-radius:100%;position:absolute;top:.125em;background-color:var(--bg);left:.125em;font-size:32px}@media only screen and (max-width:720px){input,select,textarea{width:100%}}input[type=color]{height:2.5rem;padding:.2rem}input[type=file]{border:0}hr{border:none;height:1px;background:var(--border);margin:1rem auto}mark{padding:2px 5px;border-radius:var(--standard-border-radius);background-color:var(--marked);color:#000}img,video{max-width:100%;height:auto;border-radius:var(--standard-border-radius)}figure{margin:0;display:block;overflow-x:auto}figcaption{text-align:center;font-size:.9rem;color:var(--text-light);margin-bottom:1rem}blockquote{margin:2rem 0 2rem 2rem;padding:.4rem .8rem;border-left:.35rem solid var(--accent);color:var(--text-light);font-style:italic}cite{font-size:.9rem;color:var(--text-light);font-style:normal}dt{color:var(--text-light)}code,kbd,pre,pre span,samp{font-family:var(--mono-font);color:var(--code)}kbd{color:var(--preformatted);border:1px solid var(--preformatted);border-bottom:3px solid var(--preformatted);border-radius:var(--standard-border-radius);padding:.1rem .4rem}pre{padding:1rem 1.4rem;max-width:100%;overflow:auto;color:var(--preformatted)}pre code{color:var(--preformatted);background:0 0;margin:0;padding:0}progress{width:100%}progress:indeterminate{background-color:var(--accent-bg)}progress::-webkit-progress-bar{border-radius:var(--standard-border-radius);background-color:var(--accent-bg)}progress::-webkit-progress-value{border-radius:var(--standard-border-radius);background-color:var(--accent)}progress::-moz-progress-bar{border-radius:var(--standard-border-radius);background-color:var(--accent);transition-property:width;transition-duration:.3s}progress:indeterminate::-moz-progress-bar{background-color:var(--accent-bg)}dialog{max-width:40rem;margin:auto}dialog::backdrop{background-color:var(--bg);opacity:.8}@media only screen and (max-width:720px){dialog{max-width:100%;margin:auto 1em}}.button,.button:visited{display:inline-block;text-decoration:none;border:none;border-radius:5px;background:var(--accent);font-size:1rem;color:var(--bg);padding:.7rem .9rem;margin:.5rem 0}.button:focus,.button:hover{filter:brightness(1.4);cursor:pointer}.notice{background:var(--accent-bg);border:2px solid var(--border);border-radius:5px;padding:1.5rem;margin:2rem 0}:root {--accent: #242933;}button {margin: 5px;}.center {text-align: center;}.bg-green {background-color: darkgreen;}.bg-red {background-color: darkred;}.d-none {display: none;}.tiny {font-size-adjust: 0.3}" #define SITE_CONFIRM "{{> partials/text}}
{{> partials/timeout}}" #define SITE_INPUT "{{> partials/text}}
{{> partials/timeout}}" -#define SITE_LINK "{{> partials/text}}

{{label}}

{{> partials/imgdata}}
{{> partials/timeout}}" +#define SITE_LINK "{{> partials/text}}{{#label}}

{{.}}

{{/label}}{{> partials/imgdata}}
{{> partials/timeout}}" #define SITE_MULTIPLE "{{> partials/text}}
{{> partials/timeout}}" #define SITE_PASSWORD "{{> partials/text}}
{{> partials/timeout}}" #define SITE_SELECT "{{> partials/text}}
{{#other}}{{/other}}
{{> partials/timeout}}{{^other}}{{/other}}{{#other}}{{/other}}" diff --git a/src/oidc-prompt/mustache.c b/src/oidc-prompt/mustache.c index 9495e2e0..22dd3feb 100644 --- a/src/oidc-prompt/mustache.c +++ b/src/oidc-prompt/mustache.c @@ -1,6 +1,7 @@ +#include "wrapper/mustache.h" + #include "html/templates.h" -#include "mustache-wrapper.h" #include "utils/json.h" #include "utils/oidc_error.h" #include "utils/string/stringUtils.h" diff --git a/src/oidc-prompt/oidc-prompt.c b/src/oidc-prompt/oidc-prompt.c index c4f012ae..2d8708c2 100644 --- a/src/oidc-prompt/oidc-prompt.c +++ b/src/oidc-prompt/oidc-prompt.c @@ -63,18 +63,20 @@ int main(int argc, char** argv) { return e; } char* footer = strstr((char*)passed_html, "
len : 0) + 2); html = mustache_main(SITE_MULTIPLE, data); + h_pc = 150; } else if (strstarts(prompt_type, "select")) { if (strequal(prompt_type, "select-other")) { data = jsonAddNumberValue(data, "other", 1); } if (arguments.additional_args != NULL) { data = jsonAddJSON(data, "options", - listToJSONArray(arguments.additional_args)); + stringListToJSONArray(arguments.additional_args)); } html = mustache_main(SITE_SELECT, data); } else if (strstarts(prompt_type, "link")) { @@ -116,9 +119,11 @@ int main(int argc, char** argv) { oidc_sprintf("data:image/%s;base64,%s", "svg+xml", base64); secFree(base64); data = jsonAddStringValue(data, "img-data", imgData); - h_pc = 250; + w_pc = 150; + h_pc = 235; } else { - h_pc = 150; + w_pc = 150; + h_pc = 225; } html = mustache_main(SITE_LINK, data); } else { @@ -133,6 +138,7 @@ int main(int argc, char** argv) { char* tmpFile = oidc_pathcat(tmpdir, r); secFree(r); writeFile(tmpFile, html); + /** h_pc = (int)(1.3*(float)h_pc); */ char* cmd = oidc_sprintf("oidc-webview \"%s\" \"%s\" %d %d", arguments.title, tmpFile, w_pc, h_pc); @@ -150,4 +156,4 @@ int main(int argc, char** argv) { #endif secFree(html); return 0; -} \ No newline at end of file +} diff --git a/src/oidc-prompt/oidc_webview.c b/src/oidc-prompt/oidc_webview.c index 45e009a4..866b89a3 100644 --- a/src/oidc-prompt/oidc_webview.c +++ b/src/oidc-prompt/oidc_webview.c @@ -92,7 +92,7 @@ void webview(const char* title, const char* html, int w_pc, int h_pc) { return; } int width = 480; - int height = 320; + int height = 350; if (w_pc) { width *= w_pc; width /= 100; diff --git a/src/oidc-token/oidc-token_options.c b/src/oidc-token/oidc-token_options.c index 1396e376..cfd2c4a8 100644 --- a/src/oidc-token/oidc-token_options.c +++ b/src/oidc-token/oidc-token_options.c @@ -1,5 +1,6 @@ #include "oidc-token_options.h" +#include "utils/config/client_config.h" #include "utils/memory.h" #include "utils/string/stringUtils.h" @@ -80,6 +81,8 @@ static struct argp_option options[] = { {0, 'h', 0, OPTION_HIDDEN, 0, -1}, {0, 0, 0, 0, 0, 0}}; +static unsigned char min_valid_period_set_from_arg = 0; + static error_t parse_opt(int key, char* arg, struct argp_state* state) { struct arguments* arguments = state->input; @@ -98,7 +101,8 @@ static error_t parse_opt(int key, char* arg, struct argp_state* state) { if (!isdigit(*arg)) { return ARGP_ERR_UNKNOWN; } - arguments->min_valid_period = strToInt(arg); + arguments->min_valid_period = strToInt(arg); + min_valid_period_set_from_arg = 1; break; case OPT_IDTOKEN: arguments->idtoken = 1; break; case OPT_NAME: arguments->application_name = arg; break; @@ -116,7 +120,8 @@ static error_t parse_opt(int key, char* arg, struct argp_state* state) { arguments->expiration_env.useIt = 1; break; case 'm': - arguments->mytoken.str = arg; + arguments->mytoken.str = + arg ?: getClientConfig()->default_mytoken_profile; arguments->mytoken.useIt = 1; break; case 'a': arguments->printAll = 1; break; @@ -153,7 +158,7 @@ static char doc[] = struct argp argp = {options, parse_opt, args_doc, doc, 0, 0, 0}; void initArguments(struct arguments* arguments) { - arguments->min_valid_period = 0; + arguments->min_valid_period = getClientConfig()->default_min_lifetime; arguments->args[0] = NULL; arguments->scopes = NULL; arguments->application_name = NULL; diff --git a/src/utils/accountUtils.c b/src/utils/accountUtils.c index 6fb936a3..847d9a54 100644 --- a/src/utils/accountUtils.c +++ b/src/utils/accountUtils.c @@ -4,14 +4,13 @@ #include "account/account.h" #include "utils/crypt/cryptUtils.h" -#include "utils/crypt/gpg/gpg.h" #include "utils/db/account_db.h" #include "utils/file_io/cryptFileUtils.h" #include "utils/file_io/file_io.h" #include "utils/file_io/promptCryptFileUtils.h" #include "utils/json.h" #include "utils/logger.h" -#include "utils/promptUtils.h" +#include "utils/prompting/promptUtils.h" #include "utils/string/stringUtils.h" /** diff --git a/src/utils/agentLogger.c b/src/utils/agentLogger.c index a492fc29..6e2ecb08 100644 --- a/src/utils/agentLogger.c +++ b/src/utils/agentLogger.c @@ -2,14 +2,9 @@ #include -void (*agent_log)(int log_level, const char* msg, ...); +static unsigned char _logWithTerminal = 0; -void setLogWithTerminal() { agent_log = loggerTerminal; } -void setLogWithoutTerminal() { agent_log = logger; }; +void setLogWithTerminal() { _logWithTerminal = 1; } +unsigned char logWithTerminal() { return _logWithTerminal; } -void agent_openlog(const char* logger_name) { - if (agent_log == NULL) { - setLogWithoutTerminal(); - } - logger_open(logger_name); -} +void agent_openlog(const char* logger_name) { logger_open(logger_name); } diff --git a/src/utils/agentLogger.h b/src/utils/agentLogger.h index 29474265..a3033560 100644 --- a/src/utils/agentLogger.h +++ b/src/utils/agentLogger.h @@ -3,10 +3,13 @@ #include "utils/logger.h" -void setLogWithTerminal(); -void setLogWithoutTerminal(); +void setLogWithTerminal(); +unsigned char logWithTerminal(); + +#define agent_log(LOG_LEVEL, MSG, ...) \ + logWithTerminal() ? loggerTerminal(LOG_LEVEL, MSG, ##__VA_ARGS__) \ + : logger(LOG_LEVEL, MSG, ##__VA_ARGS__) -extern void (*agent_log)(int log_level, const char* msg, ...); void agent_openlog(const char* logger_name); #endif /* OIDC_AGENT_LOGGER_H */ diff --git a/src/utils/commonFeatures.c b/src/utils/commonFeatures.c index b4b18296..0b38523e 100644 --- a/src/utils/commonFeatures.c +++ b/src/utils/commonFeatures.c @@ -20,7 +20,7 @@ void common_handleListConfiguredAccountConfigs() { printStdoutIfTTY("No account configurations created yet.\n"); exit(EXIT_SUCCESS); } - list_mergeSort(list, (int(*)(const void*, const void*))compareFilesByName); + list_mergeSort(list, (matchFunction)compareFilesByName); char* str = listToDelimitedString(list, "\n"); secFreeList(list); printStdoutIfTTY("The following account configurations are usable: \n"); diff --git a/src/utils/config/add_config.c b/src/utils/config/add_config.c new file mode 100644 index 00000000..1d9ebf92 --- /dev/null +++ b/src/utils/config/add_config.c @@ -0,0 +1,63 @@ +#include "add_config.h" + +#include + +#include "configUtils.h" +#include "defines/ipc_values.h" +#include "utils/file_io/oidc_file_io.h" +#include "utils/json.h" +#include "utils/listUtils.h" +#include "utils/prompting/prompt_mode.h" +#include "utils/string/stringUtils.h" + +void _secFreeAddConfig(add_config_t* c) { + if (c == NULL) { + return; + } + secFree(c); +} + +static add_config_t* add_config = NULL; + +static add_config_t* _getAddConfig(const char* json) { + if (NULL == json) { + return secAlloc(sizeof(add_config_t)); + } + + INIT_KEY_VALUE(CONFIG_KEY_STOREPW, CONFIG_KEY_PWPROMPTMODE, + CONFIG_KEY_DEBUGLOGGING); + if (getJSONValuesFromString(json, pairs, sizeof(pairs) / sizeof(*pairs)) < + 0) { + SEC_FREE_KEY_VALUES(); + oidc_perror(); + exit(oidc_errno); + } + KEY_VALUE_VARS(store_pw, pw_prompt, debug); + add_config_t* c = secAlloc(sizeof(add_config_t)); + c->store_pw = strToBit(_store_pw); + c->pw_prompt_mode = parse_prompt_mode(_pw_prompt); + c->debug = strToBit(_debug); + SEC_FREE_KEY_VALUES(); + return c; +} + +const add_config_t* getAddConfig() { + if (add_config != NULL) { + return add_config; + } + cJSON* json = readConfig(); + if (json == NULL) { + add_config = secAlloc(sizeof(add_config_t)); + return add_config; + } + + char* add_json = getJSONValue(json, CONFIG_KEY_ADD); + if (add_json == NULL) { + _secFreeAddConfig(add_config); + oidc_perror(); + exit(oidc_errno); + } + add_config = _getAddConfig(add_json); + secFree(add_json); + return add_config; +} diff --git a/src/utils/config/add_config.h b/src/utils/config/add_config.h new file mode 100644 index 00000000..35288cf1 --- /dev/null +++ b/src/utils/config/add_config.h @@ -0,0 +1,14 @@ +#ifndef OIDC_AGENT_ADD_CONFIG_H +#define OIDC_AGENT_ADD_CONFIG_H + +struct add_config { + unsigned char store_pw : 1; + unsigned char pw_prompt_mode : 2; + unsigned char debug : 1; +}; + +typedef struct add_config add_config_t; + +const add_config_t* getAddConfig(); + +#endif // OIDC_AGENT_ADD_CONFIG_H diff --git a/src/utils/config/agent_config.c b/src/utils/config/agent_config.c new file mode 100644 index 00000000..7717f5b0 --- /dev/null +++ b/src/utils/config/agent_config.c @@ -0,0 +1,100 @@ +#include "agent_config.h" + +#include + +#include "configUtils.h" +#include "defines/ipc_values.h" +#include "utils/file_io/oidc_file_io.h" +#include "utils/json.h" +#include "utils/listUtils.h" +#include "utils/printer.h" +#include "utils/string/stringUtils.h" + +void _secFreeAgentConfig(agent_config_t* c) { + if (c == NULL) { + return; + } + secFree(c->cert_path); + secFree(c->bind_address); + secFree(c->group); + secFree(c); +} + +static agent_config_t* agent_config = NULL; + +static agent_config_t* _getAgentConfig(const char* json) { + if (NULL == json) { + return secAlloc(sizeof(agent_config_t)); + } + + INIT_KEY_VALUE(AGENT_KEY_CERTPATH, CONFIG_KEY_BINDADDRESS, CONFIG_KEY_CONFIRM, + CONFIG_KEY_AUTOLOAD, CONFIG_KEY_AUTOREAUTH, + CONFIG_KEY_CUSTOMURISCHEME, CONFIG_KEY_WEBSERVER, + CONFIG_KEY_DEBUGLOGGING, IPC_KEY_LIFETIME, CONFIG_KEY_GROUP, + IPC_KEY_ALWAYSALLOWID, CONFIG_KEY_AUTOGEN, + CONFIG_KEY_AUTOGENSCOPEMODE, CONFIG_KEY_STATSCOLLECT, + CONFIG_KEY_STATSCOLLECTSHARE, CONFIG_KEY_STATSCOLLECTLOCATION); + if (getJSONValuesFromString(json, pairs, sizeof(pairs) / sizeof(*pairs)) < + 0) { + SEC_FREE_KEY_VALUES(); + oidc_perror(); + exit(oidc_errno); + } + KEY_VALUE_VARS(cert_path, bind_address, confirm, autoload, autoreauth, + customurischeme, webserver, debug, lifetime, group, + alwaysallowidtoken, autogen, autogenscopemode, stats_collect, + stats_collect_share, stats_collect_location); + agent_config_t* c = secAlloc(sizeof(agent_config_t)); + c->cert_path = oidc_strcopy(_cert_path); + c->bind_address = oidc_strcopy(_bind_address); + c->group = oidc_strcopy(_group); + c->confirm = strToBit(_confirm); + c->autoload = strToBit(_autoload); + c->autoreauth = strToBit(_autoreauth); + c->customurischeme = strToBit(_customurischeme); + c->webserver = strToBit(_webserver); + c->alwaysallowidtoken = strToBit(_alwaysallowidtoken); + c->autogen = strToBit(_autogen); + c->stats_collect = strToBit(_stats_collect); + c->stats_collect_share = strToBit(_stats_collect_share); + c->stats_collect_location = strToBit(_stats_collect_location); + if (strValid(_autogenscopemode)) { + if (strcaseequal(_autogenscopemode, CONFIG_VALUE_SCOPEMODE_EXACT)) { + c->autogenscopemode = AGENTCONFIG_AUTOGENSCOPEMODE_EXACT; + } else if (strcaseequal(_autogenscopemode, CONFIG_VALUE_SCOPEMODE_MAX)) { + c->autogenscopemode = AGENTCONFIG_AUTOGENSCOPEMODE_ALL; + } else { + printError( + "error in oidc-agent config: config attribute '%s' cannot have " + "value '%s'\n", + CONFIG_KEY_AUTOGENSCOPEMODE, _autogenscopemode); + SEC_FREE_KEY_VALUES(); + exit(EXIT_FAILURE); + } + } + c->debug = strToBit(_debug); + c->lifetime = strToLong(_lifetime); + SEC_FREE_KEY_VALUES(); + return c; +} + +const agent_config_t* getAgentConfig() { + if (agent_config != NULL) { + return agent_config; + } + cJSON* json = readConfig(); + if (json == NULL) { + agent_config = secAlloc(sizeof(agent_config_t)); + return agent_config; + } + + char* agent_json = getJSONValue(json, CONFIG_KEY_AGENT); + if (agent_json == NULL) { + _secFreeAgentConfig(agent_config); + oidc_perror(); + exit(oidc_errno); + } + agent_config = _getAgentConfig(agent_json); + secFree(agent_json); + return agent_config; +} diff --git a/src/utils/config/agent_config.h b/src/utils/config/agent_config.h new file mode 100644 index 00000000..470a23fb --- /dev/null +++ b/src/utils/config/agent_config.h @@ -0,0 +1,34 @@ +#ifndef OIDC_AGENT_AGENT_CONFIG_H +#define OIDC_AGENT_AGENT_CONFIG_H + +#include + +#define AGENTCONFIG_AUTOGENSCOPEMODE_ALL 0 +#define AGENTCONFIG_AUTOGENSCOPEMODE_EXACT 1 +#define AGENTCONFIG_AUTOGENSCOPEMODE_RESERVED 2 +#define AGENTCONFIG_AUTOGENSCOPEMODE_RESERVED_2 3 + +struct agent_config { + char* cert_path; + char* bind_address; + unsigned char confirm : 1; + unsigned char autoload : 1; + unsigned char autoreauth : 1; + unsigned char customurischeme : 1; + unsigned char webserver : 1; + unsigned char debug : 1; + unsigned char alwaysallowidtoken : 1; + unsigned char autogen : 1; + unsigned char autogenscopemode : 2; + unsigned char stats_collect : 1; + unsigned char stats_collect_share : 1; + unsigned char stats_collect_location : 1; + time_t lifetime; + char* group; +}; + +typedef struct agent_config agent_config_t; + +const agent_config_t* getAgentConfig(); + +#endif // OIDC_AGENT_AGENT_CONFIG_H diff --git a/src/utils/config/client_config.c b/src/utils/config/client_config.c new file mode 100644 index 00000000..96691b0f --- /dev/null +++ b/src/utils/config/client_config.c @@ -0,0 +1,62 @@ +#include "client_config.h" + +#include + +#include "configUtils.h" +#include "defines/ipc_values.h" +#include "utils/file_io/oidc_file_io.h" +#include "utils/json.h" +#include "utils/listUtils.h" +#include "utils/string/stringUtils.h" + +void _secFreeClientConfig(client_config_t* c) { + if (c == NULL) { + return; + } + secFree(c->default_mytoken_profile); + secFree(c); +} + +static client_config_t* client_config = NULL; + +static client_config_t* _getClientConfig(const char* json) { + if (NULL == json) { + return secAlloc(sizeof(client_config_t)); + } + + INIT_KEY_VALUE(CONFIG_KEY_DEFAULTMINLIFETIME, + CONFIG_KEY_DEFAULTMYTOKENPROFILE); + if (getJSONValuesFromString(json, pairs, sizeof(pairs) / sizeof(*pairs)) < + 0) { + SEC_FREE_KEY_VALUES(); + oidc_perror(); + exit(oidc_errno); + } + KEY_VALUE_VARS(default_min_lifetime, default_mytoken_profile); + client_config_t* c = secAlloc(sizeof(client_config_t)); + c->default_min_lifetime = strToLong(_default_min_lifetime); + secFree(_default_min_lifetime); + c->default_mytoken_profile = _default_mytoken_profile; + return c; +} + +const client_config_t* getClientConfig() { + if (client_config != NULL) { + return client_config; + } + cJSON* json = readConfig(); + if (json == NULL) { + client_config = secAlloc(sizeof(client_config_t)); + return client_config; + } + + char* client_json = getJSONValue(json, CONFIG_KEY_CLIENT); + if (client_json==NULL) { + _secFreeClientConfig(client_config); + oidc_perror(); + exit(oidc_errno); + } + client_config = _getClientConfig(client_json); + secFree(client_json); + return client_config; +} \ No newline at end of file diff --git a/src/utils/config/client_config.h b/src/utils/config/client_config.h new file mode 100644 index 00000000..76f2ddc4 --- /dev/null +++ b/src/utils/config/client_config.h @@ -0,0 +1,15 @@ +#ifndef OIDC_AGENT_CLIENT_CONFIG_H +#define OIDC_AGENT_CLIENT_CONFIG_H + +#include + +struct client_config { + time_t default_min_lifetime; + char* default_mytoken_profile; +}; + +typedef struct client_config client_config_t; + +const client_config_t* getClientConfig(); + +#endif // OIDC_AGENT_CLIENT_CONFIG_H diff --git a/src/utils/config/configUtils.c b/src/utils/config/configUtils.c new file mode 100644 index 00000000..890896ff --- /dev/null +++ b/src/utils/config/configUtils.c @@ -0,0 +1,80 @@ +#include "configUtils.h" + +#include "defines/msys.h" +#include "defines/settings.h" +#include "utils/file_io/file_io.h" +#include "utils/file_io/oidc_file_io.h" +#include "utils/json.h" +#include "utils/listUtils.h" +#include "utils/printer.h" +#include "utils/string/stringUtils.h" + +static char* readUserConfig() { + const char* user_config = getenv(OIDC_USER_CONFIG_PATH_ENV_NAME); + if (user_config == NULL) { + list_t* lines = getLinesFromOidcFileWithoutComments("config"); + char* data = listToDelimitedString(lines, ""); + secFreeList(lines); + return data; + } + if (strcaseequal("/dev/null", user_config)) { + return NULL; + } + list_t* lines = getLinesFromFileWithoutComments(user_config); + char* data = listToDelimitedString(lines, ""); + secFreeList(lines); + return data; +} + +static char* readGlobalConfig() { + list_t* lines = getLinesFromFileWithoutComments( +#ifdef ANY_MSYS + ETC_CONFIG_FILE() +#else + ETC_CONFIG_FILE +#endif + ); + char* data = listToDelimitedString(lines, ""); + secFreeList(lines); + return data; +} + +cJSON* readConfig() { + char* global = readGlobalConfig(); + char* user = readUserConfig(); + if (!strValid(global)) { + secFree(global); + } + if (!strValid(user)) { + secFree(user); + } + if (global == NULL && user == NULL) { + return NULL; + } + cJSON* g = stringToJson(global); + cJSON* u = stringToJson(user); + secFree(global); + secFree(user); + if (u == NULL) { + if (oidc_errno == OIDC_EJSONPARS) { + printError("error in user configuration file: %s\n", oidc_serror()); + exit(oidc_errno); + } + return g; + } + if (g == NULL) { + if (oidc_errno == OIDC_EJSONPARS) { + printError("error in global configuration file: %s\n", oidc_serror()); + exit(oidc_errno); + } + return u; + } + cJSON* c = jsonMergePatch(g, u); + secFreeJson(g); + secFreeJson(u); + if (c == NULL) { + printError("error while merging global and user config\n"); + exit(EXIT_FAILURE); + } + return c; +} diff --git a/src/utils/config/configUtils.h b/src/utils/config/configUtils.h new file mode 100644 index 00000000..91809d8d --- /dev/null +++ b/src/utils/config/configUtils.h @@ -0,0 +1,8 @@ +#ifndef OIDC_AGENT_CONFIGUTILS_H +#define OIDC_AGENT_CONFIGUTILS_H + +#include "wrapper/cjson.h" + +cJSON* readConfig(); + +#endif // OIDC_AGENT_CONFIGUTILS_H diff --git a/src/utils/config/gen_config.c b/src/utils/config/gen_config.c new file mode 100644 index 00000000..6d3cd7ee --- /dev/null +++ b/src/utils/config/gen_config.c @@ -0,0 +1,99 @@ +#include "gen_config.h" + +#include + +#include "configUtils.h" +#include "defines/ipc_values.h" +#include "utils/file_io/oidc_file_io.h" +#include "utils/json.h" +#include "utils/listUtils.h" +#include "utils/printer.h" +#include "utils/prompting/prompt_mode.h" +#include "utils/string/stringUtils.h" + +void _secFreeGenConfig(gen_config_t* c) { + if (c == NULL) { + return; + } + secFree(c->cnid); + secFree(c->default_mytoken_profile); + secFree(c->default_mytoken_server); + secFree(c->default_gpg_key); + secFree(c); +} + +static gen_config_t* gen_config = NULL; + +static gen_config_t* _getGenConfig(const char* json) { + if (NULL == json) { + return secAlloc(sizeof(gen_config_t)); + } + + INIT_KEY_VALUE(CONFIG_KEY_CNID, CONFIG_KEY_AUTOOPENURL, + CONFIG_KEY_DEFAULTGPGKEY, CONFIG_KEY_PROMPTMODE, + CONFIG_KEY_PWPROMPTMODE, CONFIG_KEY_ANSWERCONFIRMPROMPTS, + CONFIG_KEY_DEFAULTMYTOKENSERVER, + CONFIG_KEY_DEFAULTMYTOKENPROFILE, + CONFIG_KEY_PREFERMYTOKENOVEROIDC, CONFIG_KEY_DEBUGLOGGING); + if (getJSONValuesFromString(json, pairs, sizeof(pairs) / sizeof(*pairs)) < + 0) { + SEC_FREE_KEY_VALUES(); + oidc_perror(); + exit(oidc_errno); + } + KEY_VALUE_VARS(cnid, auto_open_url, default_gpg_key, prompt, pw_prompt, + answer_confirm_prompts, default_mytoken_server, + default_mytoken_profile, prefer_mytoken_over_oidc, debug); + gen_config_t* c = secAlloc(sizeof(gen_config_t)); + c->cnid = _cnid; + c->autoopenurl = strToBit(_auto_open_url); + secFree(_auto_open_url); + c->default_gpg_key = _default_gpg_key; + c->prompt_mode = parse_prompt_mode(_prompt); + secFree(_prompt); + c->pw_prompt_mode = parse_prompt_mode(_pw_prompt); + secFree(_pw_prompt); + if (strValid(_answer_confirm_prompts)) { + if (strcaseequal(_answer_confirm_prompts, "yes")) { + c->answer_confirm_prompts_mode = CONFIRM_PROMPT_MODE_YES; + } else if (strcaseequal(_answer_confirm_prompts, "no")) { + c->answer_confirm_prompts_mode = CONFIRM_PROMPT_MODE_NO; + } else if (strcaseequal(_answer_confirm_prompts, "default")) { + c->answer_confirm_prompts_mode = CONFIRM_PROMPT_MODE_DEFAULT; + } else { + printError("error in oidc-gen config: config attribute '%s' cannot have " + "value '%s'\n", + CONFIG_KEY_ANSWERCONFIRMPROMPTS, _answer_confirm_prompts); + exit(EXIT_FAILURE); + } + } + secFree(_answer_confirm_prompts); + c->default_mytoken_server = _default_mytoken_server; + c->default_mytoken_profile = _default_mytoken_profile; + c->prefer_mytoken_over_oidc = strToBit(_prefer_mytoken_over_oidc); + secFree(_prefer_mytoken_over_oidc); + c->debug = strToBit(_debug); + secFree(_debug); + return c; +} + +const gen_config_t* getGenConfig() { + if (gen_config != NULL) { + return gen_config; + } + cJSON* json = readConfig(); + if (json == NULL) { + gen_config = secAlloc(sizeof(gen_config_t)); + return gen_config; + } + + char* gen_json = getJSONValue(json, CONFIG_KEY_GEN); + if (gen_json == NULL) { + _secFreeGenConfig(gen_config); + oidc_perror(); + exit(oidc_errno); + } + gen_config = _getGenConfig(gen_json); + secFree(gen_json); + return gen_config; +} diff --git a/src/utils/config/gen_config.h b/src/utils/config/gen_config.h new file mode 100644 index 00000000..a369afd0 --- /dev/null +++ b/src/utils/config/gen_config.h @@ -0,0 +1,26 @@ +#ifndef OIDC_AGENT_GEN_CONFIG_H +#define OIDC_AGENT_GEN_CONFIG_H + +#define CONFIRM_PROMPT_MODE_UNSET 0 +#define CONFIRM_PROMPT_MODE_DEFAULT 1 +#define CONFIRM_PROMPT_MODE_NO 2 +#define CONFIRM_PROMPT_MODE_YES 3 + +struct gen_config { + char* cnid; + char* default_gpg_key; + unsigned char autoopenurl : 1; + unsigned char prompt_mode : 2; + unsigned char pw_prompt_mode : 2; + unsigned char prefer_mytoken_over_oidc : 1; + unsigned char debug : 1; + unsigned char answer_confirm_prompts_mode : 2; + char* default_mytoken_server; + char* default_mytoken_profile; +}; + +typedef struct gen_config gen_config_t; + +const gen_config_t* getGenConfig(); + +#endif // OIDC_AGENT_GEN_CONFIG_H diff --git a/src/utils/config/issuerConfig.c b/src/utils/config/issuerConfig.c new file mode 100644 index 00000000..830ea9ad --- /dev/null +++ b/src/utils/config/issuerConfig.c @@ -0,0 +1,506 @@ +#include "issuerConfig.h" + +#include +#include + +#include "defines/agent_values.h" +#include "defines/ipc_values.h" +#include "defines/oidc_values.h" +#include "defines/settings.h" +#include "utils/file_io/fileUtils.h" +#include "utils/file_io/file_io.h" +#include "utils/file_io/oidc_file_io.h" +#include "utils/json.h" +#include "utils/listUtils.h" +#include "utils/matcher.h" +#include "utils/memory.h" +#include "utils/oidc_error.h" +#include "utils/string/stringUtils.h" + +void _secFreePubclientConfig(struct clientConfig* p) { + if (p == NULL) { + return; + } + secFree(p->client_id); + secFree(p->client_secret); + secFree(p->scope); + secFreeList(p->flows); + secFree(p); +} + +void _secFreeIssuerConfig(struct issuerConfig* c) { + if (c == NULL) { + return; + } + secFree(c->issuer); + secFree(c->contact); + secFree(c->manual_register); + secFree(c->configuration_endpoint); + secFree(c->device_authorization_endpoint); + secFree(c->cert_path); + secFreePubclientConfig(c->pub_client); + secFree(c->default_account); + secFreeList(c->accounts); + secFree(c); +} + +struct clientConfig* getClientConfigFromJSON(const char* json) { + if (NULL == json) { + oidc_setArgNullFuncError(__func__); + return NULL; + } + INIT_KEY_VALUE(OIDC_KEY_CLIENTID, OIDC_KEY_CLIENTSECRET, OIDC_KEY_SCOPE, + IPC_KEY_FLOW, OIDC_KEY_REDIRECTURIS); + GET_JSON_VALUES_RETURN_NULL_ONERROR(json); + KEY_VALUE_VARS(client_id, client_secret, scope, flow, redirect_uris); + struct clientConfig* p = secAlloc(sizeof(struct clientConfig)); + p->client_id = _client_id; + p->client_secret = _client_secret; + p->scope = _scope; + p->flows = JSONArrayStringToList(_flow); + p->redirect_uris = JSONArrayStringToList(_redirect_uris); + secFree(_flow); + return p; +} + +struct issuerConfig* getIssuerConfigFromJSON(const cJSON* json) { + if (NULL == json) { + oidc_setArgNullFuncError(__func__); + return NULL; + } + INIT_KEY_VALUE( + AGENT_KEY_ISSUERURL, OIDC_KEY_ISSUER, AGENT_KEY_CONFIG_ENDPOINT, + AGENT_KEY_CERTPATH, OIDC_KEY_DEVICE_AUTHORIZATION_ENDPOINT, + AGENT_KEY_OAUTH, CONFIG_KEY_LEGACYAUDMODE, AGENT_KEY_PUBCLIENT, + AGENT_KEY_MANUAL_CLIENT_REGISTRATION_URI, AGENT_KEY_CONTACT, + AGENT_KEY_PWSTORE, AGENT_KEY_DEFAULT_ACCOUNT, AGENT_KEY_ACCOUNTS); + GET_JSON_VALUES_CJSON_RETURN_NULL_ONERROR(json); + KEY_VALUE_VARS(issuer_url, issuer, config_endpoint, cert_path, + device_authorization_endpoint, oauth, legacy_aud_mode, + pub_client, manual_register, contact, store_pw, + default_account, accounts); + struct issuerConfig* c = secAlloc(sizeof(struct issuerConfig)); + if (_issuer) { + c->issuer = _issuer; + secFree(_issuer_url); + } else { + c->issuer = _issuer_url; + } + c->configuration_endpoint = _config_endpoint; + c->device_authorization_endpoint = _device_authorization_endpoint; + c->cert_path = _cert_path; + c->manual_register = _manual_register; + c->contact = _contact; + c->default_account = _default_account; + c->oauth = strToBit(_oauth); + c->oauth_set = _oauth != NULL; + c->store_pw = strToBit(_store_pw); + c->store_pw_set = _store_pw != NULL; + c->legacy_aud_mode = strToBit(_legacy_aud_mode); + c->pub_client = getClientConfigFromJSON(_pub_client); + c->accounts = JSONArrayStringToList(_accounts); + secFree(_oauth); + secFree(_store_pw); + secFree(_pub_client); + secFree(_accounts); + return c; +} + +cJSON* clientConfigToJSON(const struct clientConfig* p) { + if (p == NULL) { + return NULL; + } + cJSON* json = + generateJSONObject(OIDC_KEY_CLIENTID, cJSON_String, p->client_id, NULL); + jsonAddStringValue(json, OIDC_KEY_CLIENTSECRET, p->client_secret); + jsonAddStringValue(json, OIDC_KEY_SCOPE, p->scope); + if (p->flows) { + cJSON* flows = stringListToJSONArray(p->flows); + jsonAddJSON(json, IPC_KEY_FLOW, flows); + } + if (p->redirect_uris) { + cJSON* redirect_uris = stringListToJSONArray(p->redirect_uris); + jsonAddJSON(json, OIDC_KEY_REDIRECTURIS, redirect_uris); + } + return json; +} + +cJSON* issuerConfigToJSON(const struct issuerConfig* c) { + if (c == NULL) { + return NULL; + } + cJSON* json = + generateJSONObject(OIDC_KEY_ISSUER, cJSON_String, c->issuer ?: "", NULL); + if (c->accounts) { + cJSON* accounts = stringListToJSONArray(c->accounts); + jsonAddJSON(json, AGENT_KEY_ACCOUNTS, accounts); + } + if (c->pub_client) { + cJSON* pubclient = clientConfigToJSON(c->pub_client); + jsonAddJSON(json, AGENT_KEY_PUBCLIENT, pubclient); + } + jsonAddStringValue(json, AGENT_KEY_CONFIG_ENDPOINT, + c->configuration_endpoint); + jsonAddStringValue(json, AGENT_KEY_CERTPATH, c->cert_path); + jsonAddStringValue(json, OIDC_KEY_DEVICE_AUTHORIZATION_ENDPOINT, + c->device_authorization_endpoint); + jsonAddStringValue(json, AGENT_KEY_MANUAL_CLIENT_REGISTRATION_URI, + c->manual_register); + jsonAddStringValue(json, AGENT_KEY_CONTACT, c->contact); + jsonAddStringValue(json, AGENT_KEY_DEFAULT_ACCOUNT, c->default_account); + if (c->store_pw_set) { + jsonAddBoolValue(json, AGENT_KEY_PWSTORE, c->store_pw); + } + if (c->oauth_set) { + jsonAddBoolValue(json, AGENT_KEY_OAUTH, c->oauth); + } + if (c->legacy_aud_mode) { + jsonAddBoolValue(json, CONFIG_KEY_LEGACYAUDMODE, c->legacy_aud_mode); + } + return json; +} + +static cJSON* collection = NULL; + +static void collect_handleObject(cJSON* j) { + char* iss = getJSONValue(j, OIDC_KEY_ISSUER); + cJSON* parent = cJSON_CreateObject(); + cJSON_AddItemReferenceToObject(parent, iss, j); + secFree(iss); + cJSON* tmp = jsonMergePatch(parent, collection); + secFreeJson(parent); + secFreeJson(collection); + collection = tmp; +} + +static void collectJSONIssuers(const char* json) { + if (json == NULL) { + return; + } + if (collection == NULL) { + collection = cJSON_CreateObject(); + } + cJSON* j = cJSON_Parse(json); + if (j == NULL) { + return; + } + if (cJSON_IsObject(j)) { + collect_handleObject(j); + } else if (cJSON_IsArray(j)) { + j = j->child; + while (j) { + collect_handleObject(j); + j = j->next; + } + } +} + +static list_t* _issuers = NULL; + +static int issuerConfig_matchByIssuerUrl(const struct issuerConfig* c1, + const struct issuerConfig* c2) { + return matchUrls(c1->issuer, c2->issuer); +} + +static int issuerConfig_compByAccountCount(const struct issuerConfig* c1, + const struct issuerConfig* c2) { + // Since we want to order ascending we return 1 if c1accounts == c2->accounts) { + return 0; + } + if (c1->accounts == NULL) { + return 1; + } + if (c2->accounts == NULL) { + return -1; + } + unsigned int l1 = c1->accounts->len; + unsigned int l2 = c2->accounts->len; + if (l1 == l2) { + return 0; + } + return l1 < l2 ? 1 : -1; +} + +static list_t* assert_issuers() { + if (_issuers == NULL) { + _issuers = list_new(); + _issuers->free = (freeFunction)_secFreeIssuerConfig; + _issuers->match = (matchFunction)issuerConfig_matchByIssuerUrl; + } + return _issuers; +} + +static char* updateIssuerConfigFileFormat(char* content) { + cJSON* iss_list_json = cJSON_CreateArray(); + char* elem = strtok(content, "\n"); + while (elem != NULL) { + char* space = strchr(elem, ' '); + if (space) { + *space = '\0'; + } + char* iss = elem; + const char* default_account = space ? space + 1 : NULL; + list_t* accounts = newListWithSingleValue(default_account); + const struct issuerConfig this_config = { + .issuer = iss, + .accounts = accounts, + }; + cJSON_AddItemToArray(iss_list_json, issuerConfigToJSON(&this_config)); + secFreeList(accounts); + elem = strtok(NULL, "\n"); + } + secFree(content); + char* new_content = jsonToString(iss_list_json); + secFreeJson(iss_list_json); + writeOidcFile(ISSUER_CONFIG_FILENAME, new_content); + return new_content; +} + +static void readIssuerConfigs() { + char* content = readOidcFile(ISSUER_CONFIG_FILENAME); + if (!isJSONArray(content)) { // old config file + content = updateIssuerConfigFileFormat(content); + } + collectJSONIssuers(content); + secFree(content); + + char* oidcIssuerConfDir = concatToOidcDir(ISSUER_CONFIG_DIRNAME); + list_t* conf_list = + getFileListForDir(oidcIssuerConfDir, ISSUER_CONFIG_DIRNAME); + secFree(oidcIssuerConfDir); + if (conf_list) { + list_mergeSort(conf_list, (matchFunction)compareOidcFilesByDateModified); + list_iterator_t* it = list_iterator_new(conf_list, LIST_HEAD); + list_node_t* node; + while ((node = list_iterator_next(it))) { + content = readOidcFile(node->val); + collectJSONIssuers(content); + secFree(content); + } + secFreeList(conf_list); + list_iterator_destroy(it); + } + + content = readFile( +#ifdef ANY_MSYS + ETC_ISSUER_CONFIG_FILE() +#else + ETC_ISSUER_CONFIG_FILE +#endif + ); + collectJSONIssuers(content); + secFree(content); + + const char* etc_iss_dir = +#ifdef ANY_MSYS + ETC_ISSUER_CONFIG_DIR(); +#else + ETC_ISSUER_CONFIG_DIR; +#endif + conf_list = getFileListForDir(etc_iss_dir, etc_iss_dir); + if (conf_list) { + list_iterator_t* it = list_iterator_new(conf_list, LIST_HEAD); + list_node_t* node; + while ((node = list_iterator_next(it))) { + content = readFile(node->val); + collectJSONIssuers(content); + secFree(content); + } + secFreeList(conf_list); + list_iterator_destroy(it); + } + + cJSON* item = collection->child; + do { + struct issuerConfig* issConfig = getIssuerConfigFromJSON(item); + list_lpush(assert_issuers(), list_node_new(issConfig)); + item = item->next; + } while (item); + secFreeJson(collection); + list_mergeSort(assert_issuers(), + (matchFunction)issuerConfig_compByAccountCount); +} + +static list_t* issuers() { + if (_issuers == NULL) { + assert_issuers(); + readIssuerConfigs(); + } + return _issuers; +} + +list_t* getSuggestableIssuers() { + list_t* suggestions = list_new(); + suggestions->match = (matchFunction)matchUrls; + list_iterator_t* it = list_iterator_new(issuers(), LIST_HEAD); + list_node_t* node = NULL; + while ((node = list_iterator_next(it))) { + struct issuerConfig* c = node->val; + if (c) { + list_addStringIfNotFound(suggestions, c->issuer); + } + } + list_iterator_destroy(it); + return suggestions; +} + +list_node_t* getIssuerNode(const char* iss) { + struct issuerConfig key = {.issuer = (char*)iss}; + return findInList(issuers(), &key); +} + +const struct issuerConfig* getIssuerConfig(const char* iss) { + const list_node_t* node = getIssuerNode(iss); + return node ? node->val : NULL; +} + +list_t* defaultRedirectURIs() { + list_t* redirect_uris = + createList(0, "http://localhost:8080", "http://localhost:4242", + "http://localhost:43985", NULL); + redirect_uris->match = (matchFunction)strequal; + return redirect_uris; +} + +/** + * @brief updates the issuer.config file. + * Adds an account for an issuer + * If the issuer url is not already in the issuer.config file, it will be added. + * @param issuer_url the issuer url to be added + * @param shortname of the account config + */ +void oidcp_updateIssuerConfigAdd(const char* issuer_url, + const char* shortname) { + if (issuer_url == NULL || shortname == NULL) { + return; + } + list_node_t* node = getIssuerNode(issuer_url); + if (node == NULL) { + struct issuerConfig* c = secAlloc(sizeof(struct issuerConfig)); + c->issuer = oidc_strcopy(issuer_url); + c->accounts = newListWithSingleValue(shortname); + list_rpush(issuers(), list_node_new(c)); + } else { + struct issuerConfig* c = node->val; + if (c->accounts) { + if (findInList(c->accounts, shortname)) { + return; + } + list_addStringIfNotFound(c->accounts, (char*)shortname); + } else { + c->accounts = newListWithSingleValue(shortname); + } + } + cJSON* iss_list_json = + listToJSONArray(issuers(), (cJSON * (*)(void*)) issuerConfigToJSON); + char* new_content = jsonToString(iss_list_json); + secFreeJson(iss_list_json); + writeOidcFile(ISSUER_CONFIG_FILENAME, new_content); + secFree(new_content); +} + +/** + * @brief updates the issuer.config file. + * Adds an account for an issuer + * If the issuer url is not already in the issuer.config file, it will be added. + * @param issuer_url the issuer url to be added + * @param shortname of the account config + */ +void oidcp_updateIssuerConfigDelete(const char* issuer_url, + const char* shortname) { + if (issuer_url == NULL || shortname == NULL) { + return; + } + list_node_t* node = getIssuerNode(issuer_url); + if (node == NULL) { + return; + } else { + struct issuerConfig* c = node->val; + list_removeIfFound(c->accounts, (char*)shortname); + } + cJSON* iss_list_json = + listToJSONArray(issuers(), (cJSON * (*)(void*)) issuerConfigToJSON); + char* new_content = jsonToString(iss_list_json); + secFreeJson(iss_list_json); + writeOidcFile(ISSUER_CONFIG_FILENAME, new_content); + secFree(new_content); +} + +void oidcp_updateIssuerConfig(const char* action, const char* issuer, + const char* shortname) { + if (strequal(action, INT_ACTION_VALUE_ADD)) { + oidcp_updateIssuerConfigAdd(issuer, shortname); + } else if (strequal(action, INT_ACTION_VALUE_REMOVE)) { + oidcp_updateIssuerConfigDelete(issuer, shortname); + } +} + +const list_t* getUserClientFlows(const char* issuer_url) { + const struct issuerConfig* iss = getIssuerConfig(issuer_url); + if (iss == NULL) { + return NULL; + } + const struct clientConfig* client = iss->user_client; + if (client == NULL) { + return NULL; + } + return client->flows; +} + +const list_t* getPubClientFlows(const char* issuer_url) { + const struct issuerConfig* iss = getIssuerConfig(issuer_url); + if (iss == NULL) { + return NULL; + } + const struct clientConfig* pub = iss->pub_client; + if (pub == NULL) { + return NULL; + } + return pub->flows; +} + +char* getAccountInfos(list_t* loaded) { + cJSON* json = cJSON_CreateObject(); + list_iterator_t* it = list_iterator_new(issuers(), LIST_HEAD); + list_node_t* node = NULL; + while ((node = list_iterator_next(it))) { + struct issuerConfig* c = node->val; + if (c == NULL) { + continue; + } + cJSON* issObj = cJSON_CreateObject(); + cJSON_AddBoolToObject( + issObj, ACCOUNTINFO_KEY_HASPUBCLIENT, + c->pub_client != NULL && c->pub_client->client_id != NULL); + if (c->accounts) { + cJSON* accounts = cJSON_CreateObject(); + list_iterator_t* accounts_it = list_iterator_new(c->accounts, LIST_HEAD); + list_node_t* accounts_node = NULL; + while ((accounts_node = list_iterator_next(accounts_it))) { + const char* shortname = accounts_node->val; + if (shortname == NULL) { + continue; + } + cJSON_AddBoolToObject(accounts, shortname, + findInList(loaded, shortname) != NULL); + } + list_iterator_destroy(accounts_it); + cJSON_AddItemToObject(issObj, AGENT_KEY_ACCOUNTS, accounts); + } + cJSON_AddItemToObject(json, c->issuer, issObj); + } + list_iterator_destroy(it); + char* json_str = jsonToStringUnformatted(json); + secFreeJson(json); + return json_str; +} \ No newline at end of file diff --git a/src/utils/config/issuerConfig.h b/src/utils/config/issuerConfig.h new file mode 100644 index 00000000..53323977 --- /dev/null +++ b/src/utils/config/issuerConfig.h @@ -0,0 +1,58 @@ +#ifndef OIDC_AGENT_ISSUERCONFIG_H +#define OIDC_AGENT_ISSUERCONFIG_H + +#include "wrapper/list.h" + +struct clientConfig { + char* client_id; + char* client_secret; + char* scope; + list_t* flows; + list_t* redirect_uris; +}; + +struct issuerConfig { + char* issuer; + char* manual_register; + char* contact; + char* configuration_endpoint; + char* device_authorization_endpoint; + char* cert_path; + unsigned char store_pw : 1; + unsigned char store_pw_set : 1; + unsigned char oauth : 1; + unsigned char oauth_set : 1; + unsigned char legacy_aud_mode : 1; + struct clientConfig* pub_client; + struct clientConfig* user_client; + + char* default_account; + list_t* accounts; +}; + +const struct issuerConfig* getIssuerConfig(const char* iss); +const list_t* getPubClientFlows(const char* issuer_url); +const list_t* getUserClientFlows(const char* issuer_url); +list_t* getSuggestableIssuers(); +list_t* defaultRedirectURIs(); +void oidcp_updateIssuerConfig(const char* action, const char* issuer, + const char* shortname); +char* getAccountInfos(list_t* loaded); + +#ifndef secFreeIssuerConfig +#define secFreeIssuerConfig(ptr) \ + do { \ + _secFreeIssuerConfig((ptr)); \ + (ptr) = NULL; \ + } while (0) +#endif // secFreeIssuerConfig + +#ifndef secFreePubclientConfig +#define secFreePubclientConfig(ptr) \ + do { \ + _secFreePubclientConfig((ptr)); \ + (ptr) = NULL; \ + } while (0) +#endif // secFreePubclientConfig + +#endif // OIDC_AGENT_ISSUERCONFIG_H diff --git a/src/utils/file_io/fileUtils.c b/src/utils/file_io/fileUtils.c index 5868669c..9dce71e3 100644 --- a/src/utils/file_io/fileUtils.c +++ b/src/utils/file_io/fileUtils.c @@ -58,14 +58,14 @@ void assertOidcDirExists() { } } -list_t* getFileListForDirIf(const char* dirname, +list_t* getFileListForDirIf(const char* dirname, const char* prefix, int(match(const char*, const char*)), const char* arg) { DIR* dir; struct dirent* ent; if ((dir = opendir(dirname)) != NULL) { list_t* list = list_new(); - list->free = (void(*)(void*)) & _secFree; + list->free = (void (*)(void*)) & _secFree; list->match = (matchFunction)strequal; while ((ent = readdir(dir)) != NULL) { #ifdef _DIRENT_HAVE_D_TYPE @@ -74,7 +74,7 @@ list_t* getFileListForDirIf(const char* dirname, } #endif if (!strstarts(ent->d_name, ".") && match(ent->d_name, arg)) { - list_rpush(list, list_node_new(oidc_strcopy(ent->d_name))); + list_rpush(list, list_node_new(oidc_pathcat(prefix, ent->d_name))); } } closedir(dir); @@ -91,8 +91,8 @@ int alwaysOne(const char* a __attribute__((unused)), return 1; } -list_t* getFileListForDir(const char* dirname) { - return getFileListForDirIf(dirname, &alwaysOne, NULL); +list_t* getFileListForDir(const char* dirname, const char* prefix) { + return getFileListForDirIf(dirname, prefix, &alwaysOne, NULL); } int isClientConfigFile(const char* filename, @@ -120,9 +120,15 @@ int isAccountConfigFile(const char* filename, if (isClientConfigFile(filename, a)) { return 0; } + if (strequal(filename, "config")) { + return 0; + } if (strEnds(filename, ".config")) { return 0; } + if (strEnds(filename, ".stats")) { + return 0; + } if (strEnds(filename, ".log")) { return 0; } @@ -134,7 +140,8 @@ list_t* getAccountConfigFileList() { if (oidc_dir == NULL) { return NULL; } - list_t* list = getFileListForDirIf(oidc_dir, &isAccountConfigFile, NULL); + list_t* list = + getFileListForDirIf(oidc_dir, NULL, &isAccountConfigFile, NULL); secFree(oidc_dir); return list; } @@ -144,8 +151,8 @@ list_t* getClientConfigFileList() { if (oidc_dir == NULL) { return NULL; } - list_t* list = getFileListForDirIf(oidc_dir, &isClientConfigFile, NULL); - list_node_t* node; + list_t* list = getFileListForDirIf(oidc_dir, NULL, &isClientConfigFile, NULL); + list_node_t* node; list_iterator_t* it = list_iterator_new(list, LIST_HEAD); while ((node = list_iterator_next(it))) { char* old = node->val; @@ -264,7 +271,7 @@ oidc_error_t changeGroup(const char* path, const char* group_name) { secFree(buf); return oidc_errno; } - int err = fchmod(fd, buf->st_mode | S_ISGID | S_IRWXG); + int err = fchmod(fd, buf->st_mode | S_IRWXG); close(fd); secFree(buf); if (err != 0) { diff --git a/src/utils/file_io/fileUtils.h b/src/utils/file_io/fileUtils.h index a393877c..d4cc300d 100644 --- a/src/utils/file_io/fileUtils.h +++ b/src/utils/file_io/fileUtils.h @@ -20,6 +20,8 @@ int compareOidcFilesByDateAccessed(const char* filename1, char* generateClientConfigFileName(const char* issuer_url, const char* client_id); +list_t* getFileListForDir(const char* dirname, const char* prefix); + #ifndef MINGW oidc_error_t changeGroup(const char* path, const char* group_name); #endif diff --git a/src/utils/file_io/file_io.c b/src/utils/file_io/file_io.c index 40f3a4e3..59ad268d 100644 --- a/src/utils/file_io/file_io.c +++ b/src/utils/file_io/file_io.c @@ -53,6 +53,9 @@ oidc_error_t readBinaryFILE(FILE* fp, char** buffer, size_t* size) { } long lSize = ftell(fp); rewind(fp); + if (lSize == 0) { + return readBinaryFILE2(fp, buffer, size); + } if (lSize < 0) { oidc_setErrnoError(); logger(ERROR, "%s", oidc_serror()); @@ -67,6 +70,7 @@ oidc_error_t readBinaryFILE(FILE* fp, char** buffer, size_t* size) { return oidc_errno; } + logger(DEBUG, "Trying to read %d bytes", lSize); if (1 != fread(*buffer, lSize, 1, fp)) { if (feof(fp)) { oidc_errno = OIDC_EEOF; @@ -74,7 +78,8 @@ oidc_error_t readBinaryFILE(FILE* fp, char** buffer, size_t* size) { oidc_errno = OIDC_EFREAD; } secFree(*buffer); - logger(ERROR, "entire read failed in function %s", __func__); + logger(ERROR, "entire read failed in function %s with %d", __func__, + oidc_errno); return oidc_errno; } *size = lSize; @@ -260,6 +265,66 @@ oidc_error_t createDir(const char* path) { */ int removeFile(const char* path) { return unlink(path); } +char* getFileContentAfterLine(const char* path, const char* lineContentPrefix, + long startChar, unsigned char allIfLineNotFound) { + if (path == NULL) { + oidc_setArgNullFuncError(__func__); + return NULL; + } + logger(DEBUG, "Getting Lines from file: %s", path); + FILE* fp = fopen(path, "r"); + if (fp == NULL) { + oidc_setErrnoError(); + return NULL; + } + if (startChar > 0) { + fseek(fp, startChar, SEEK_SET); + } + + list_t* lines = list_new(); + lines->free = _secFree; + + char* line = NULL; + size_t len = 0; + unsigned char found = 0; + while (getline(&line, &len, fp) != -1) { + if (lastChar(line) == '\n') { + lastChar(line) = '\0'; + if (strlen(line) > 0 && lastChar(line) == '\r') { + lastChar(line) = '\0'; + } + } + if (found) { + list_rpush(lines, list_node_new(oidc_strcopy(line))); + } + if (strstarts(line, lineContentPrefix)) { + if (found) { + secFreeList(lines); + lines = list_new(); + lines->free = _secFree; + } + found = 1; + } + secFreeN(line, len); + } + secFreeN(line, len); + if (!found && allIfLineNotFound) { + secFreeList(lines); + rewind(fp); + char* completeContent = readFILE(fp); + if (startChar == 0) { + return completeContent; + } + char* ret = oidc_strcopy(completeContent + startChar); + secFree(completeContent); + return ret; + } + fclose(fp); + char* all = listToDelimitedString(lines, "\n"); + secFreeList(lines); + return all; +} + list_t* _getLinesFromFile(const char* path, const unsigned char ignoreComments, const char commentChar) { if (path == NULL) { @@ -277,13 +342,12 @@ list_t* _getLinesFromFile(const char* path, const unsigned char ignoreComments, lines->free = _secFree; lines->match = (matchFunction)strequal; - char* line = NULL; - size_t len = 0; - ssize_t read = 0; - while ((read = getline(&line, &len, fp)) != -1) { + char* line = NULL; + size_t len = 0; + while (getline(&line, &len, fp) != -1) { if (lastChar(line) == '\n') { lastChar(line) = '\0'; - if (lastChar(line) == '\r') { + if (strlen(line) > 0 && lastChar(line) == '\r') { lastChar(line) = '\0'; } } @@ -395,4 +459,4 @@ char* getExistingLocation(list_t* possibleLocations) { } list_iterator_destroy(it); return NULL; -} +} \ No newline at end of file diff --git a/src/utils/file_io/file_io.h b/src/utils/file_io/file_io.h index e6db1be8..915a9d8a 100644 --- a/src/utils/file_io/file_io.h +++ b/src/utils/file_io/file_io.h @@ -27,6 +27,8 @@ oidc_error_t createDir(const char* path); int removeFile(const char* path); list_t* getLinesFromFile(const char* path); list_t* getLinesFromFileWithoutComments(const char* path); +char* getFileContentAfterLine(const char* path, const char* lineContentPrefix, + long startChar, unsigned char allIfLineNotFound); oidc_error_t mkpath(const char* p, mode_t mode); char* getExistingLocation(list_t* possibleLocations); diff --git a/src/utils/file_io/oidc_file_io.c b/src/utils/file_io/oidc_file_io.c index 5472a18f..3e6823b4 100644 --- a/src/utils/file_io/oidc_file_io.c +++ b/src/utils/file_io/oidc_file_io.c @@ -139,11 +139,7 @@ oidc_error_t createOidcDir() { #else oidc_error_t ret = createDir(path); #endif - char* issuerconfig_path = oidc_pathcat(path, ISSUER_CONFIG_FILENAME); secFree(path); - int fd = open(issuerconfig_path, O_WRONLY | O_CREAT | O_TRUNC, 0644); - close(fd); - secFree(issuerconfig_path); return ret; } @@ -181,35 +177,13 @@ list_t* getLinesFromOidcFileWithoutComments(const char* filename) { return ret; } -/** - * @brief updates the issuer.config file. - * If the issuer url is not already in the issuer.config file, it will be added. - * @param issuer_url the issuer url to be added - * @param shortname will be used as the default account config for this issuer - */ -void updateIssuerConfig(const char* issuer_url, const char* shortname) { - if (issuer_url == NULL || shortname == NULL) { - return; - } - char* issuers = NULL; - if (oidcFileDoesExist(ISSUER_CONFIG_FILENAME)) { - issuers = readOidcFile(ISSUER_CONFIG_FILENAME); - } - char* new_issuers; - if (issuers) { - if (strSubStringCase(issuers, issuer_url)) { - secFree(issuers); - return; - } - new_issuers = oidc_sprintf("%s\n%s %s", issuers, issuer_url, shortname); - secFree(issuers); - } else { - new_issuers = oidc_sprintf("%s %s", issuer_url, shortname); - } - if (new_issuers == NULL) { - logger(ERROR, "%s", oidc_serror()); - } else { - writeOidcFile(ISSUER_CONFIG_FILENAME, new_issuers); - secFree(new_issuers); - } +char* getFileContentFromOidcFileAfterLine(const char* filename, + const char* lineContentPrefix, + long startChar, + unsigned char allIfLineNotFound) { + char* path = concatToOidcDir(filename); + char* ret = getFileContentAfterLine(path, lineContentPrefix, startChar, + allIfLineNotFound); + secFree(path); + return ret; } diff --git a/src/utils/file_io/oidc_file_io.h b/src/utils/file_io/oidc_file_io.h index 4e208693..0bea8638 100644 --- a/src/utils/file_io/oidc_file_io.h +++ b/src/utils/file_io/oidc_file_io.h @@ -12,8 +12,11 @@ char* readOidcFile(const char* filename); int oidcFileDoesExist(const char* filename); int removeOidcFile(const char* filename); char* concatToOidcDir(const char* filename); -void updateIssuerConfig(const char* issuer_url, const char* shortname); list_t* getLinesFromOidcFile(const char* filename); list_t* getLinesFromOidcFileWithoutComments(const char* filename); +char* getFileContentFromOidcFileAfterLine(const char* path, + const char* lineContentPrefix, + long startChar, + unsigned char allIfLineNotFound); #endif // OIDC_FILE_IO_H diff --git a/src/utils/file_io/promptCryptFileUtils.c b/src/utils/file_io/promptCryptFileUtils.c index 60b81e6a..4fce1f57 100644 --- a/src/utils/file_io/promptCryptFileUtils.c +++ b/src/utils/file_io/promptCryptFileUtils.c @@ -4,12 +4,13 @@ #include "utils/file_io/file_io.h" #include "utils/file_io/oidc_file_io.h" #include "utils/memory.h" -#include "utils/promptUtils.h" +#include "utils/prompting/promptUtils.h" oidc_error_t _promptAndCryptAndWriteToAnyFile( const char* text, const char* filepath, const char* oidc_filename, const char* hint, const char* suggestedPassword, const char* pw_cmd, - const char* pw_file, const char* pw_env, const char* gpg_key) { + const char* pw_file, const char* pw_env, const char* gpg_key, + oidc_error_t (*callback)(const char*, const char*, const char*)) { if (text == NULL || hint == NULL || (filepath == NULL && oidc_filename == NULL)) { oidc_setArgNullFuncError(__func__); @@ -28,6 +29,9 @@ oidc_error_t _promptAndCryptAndWriteToAnyFile( } oidc_error_t ret = encryptAndWriteFnc(text, oidc_filename ?: filepath, encryptionPassword, gpg_key); + if (ret == OIDC_SUCCESS && callback != NULL) { + ret = callback(text, oidc_filename ?: filepath, encryptionPassword); + } secFree(encryptionPassword); return ret; } @@ -35,27 +39,29 @@ oidc_error_t _promptAndCryptAndWriteToAnyFile( oidc_error_t promptEncryptAndWriteToFile( const char* text, const char* filepath, const char* hint, const char* suggestedPassword, const char* pw_cmd, const char* pw_file, - const char* pw_env, const char* gpg_key) { + const char* pw_env, const char* gpg_key, + oidc_error_t (*callback)(const char*, const char*, const char*)) { if (text == NULL || filepath == NULL || hint == NULL) { oidc_setArgNullFuncError(__func__); return oidc_errno; } return _promptAndCryptAndWriteToAnyFile(text, filepath, NULL, hint, suggestedPassword, pw_cmd, pw_file, - pw_env, gpg_key); + pw_env, gpg_key, callback); } oidc_error_t promptEncryptAndWriteToOidcFile( const char* text, const char* filename, const char* hint, const char* suggestedPassword, const char* pw_cmd, const char* pw_file, - const char* pw_env, const char* gpg_key) { + const char* pw_env, const char* gpg_key, + oidc_error_t (*callback)(const char*, const char*, const char*)) { if (text == NULL || filename == NULL || hint == NULL) { oidc_setArgNullFuncError(__func__); return oidc_errno; } return _promptAndCryptAndWriteToAnyFile(text, NULL, filename, hint, suggestedPassword, pw_cmd, pw_file, - pw_env, gpg_key); + pw_env, gpg_key, callback); } struct resultWithEncryptionPassword getDecryptedFileAndPasswordFor( diff --git a/src/utils/file_io/promptCryptFileUtils.h b/src/utils/file_io/promptCryptFileUtils.h index fc16b213..db67a4fb 100644 --- a/src/utils/file_io/promptCryptFileUtils.h +++ b/src/utils/file_io/promptCryptFileUtils.h @@ -7,11 +7,13 @@ oidc_error_t promptEncryptAndWriteToFile( const char* text, const char* filepath, const char* hint, const char* suggestedPassword, const char* pw_cmd, const char* pw_file, - const char* pw_env, const char* gpg_key); + const char* pw_env, const char* gpg_key, + oidc_error_t (*callback)(const char*, const char*, const char*)); oidc_error_t promptEncryptAndWriteToOidcFile( const char* text, const char* filename, const char* hint, const char* suggestedPassword, const char* pw_cmd, const char* pw_file, - const char* pw_env, const char* gpg_key); + const char* pw_env, const char* gpg_key, + oidc_error_t (*callback)(const char*, const char*, const char*)); struct resultWithEncryptionPassword getDecryptedFileAndPasswordFor( const char* filepath, const char* pw_cmd, const char* pw_file, const char* pw_env); diff --git a/src/utils/file_io/safefile/check_file_path.c b/src/utils/file_io/safefile/check_file_path.c new file mode 100644 index 00000000..a77d7795 --- /dev/null +++ b/src/utils/file_io/safefile/check_file_path.c @@ -0,0 +1,84 @@ +#include "check_file_path.h" + +#include +#include + +#include "safe_id_range_list.h" +#include "safe_is_path_trusted.h" +#include "utils/oidc_error.h" + +oidc_error_t check_socket_path(const char* path, const char* group) { + struct safe_id_range_list ulist, glist; + + /* initialize the lists of trusted uid/gid, can basically only fail when + * out of memory. + */ + if (safe_init_id_range_list(&ulist)) { + oidc_errno = OIDC_EMEM; + return oidc_errno; + } + if (safe_add_id_to_list(&ulist, (id_t)getuid())) { + safe_destroy_id_range_list(&ulist); + oidc_errno = OIDC_EMEM; + return oidc_errno; + } + if (safe_init_id_range_list(&glist)) { + safe_destroy_id_range_list(&ulist); + oidc_errno = OIDC_EMEM; + return oidc_errno; + } + if (safe_add_id_to_list(&glist, (id_t)getgid())) { + safe_destroy_id_range_list(&ulist); + safe_destroy_id_range_list(&glist); + oidc_errno = OIDC_EMEM; + return oidc_errno; + } + if (group) { + struct group* grp = getgrnam(group); + if (grp == NULL) { + if (errno == 0) { + oidc_errno = OIDC_EGROUPNF; + } else { + oidc_setErrnoError(); + } + safe_destroy_id_range_list(&ulist); + safe_destroy_id_range_list(&glist); + return oidc_errno; + } + if (safe_add_id_to_list(&glist, (id_t)grp->gr_gid)) { + safe_destroy_id_range_list(&ulist); + safe_destroy_id_range_list(&glist); + oidc_errno = OIDC_EMEM; + return oidc_errno; + } + } + + /* Check whether file is trusted */ + int trust = safe_is_path_trusted_r(path, &ulist, &glist); + /* Check the level of trust */ + switch (trust) { + case SAFE_PATH_TRUSTED_CONFIDENTIAL: + oidc_errno = OIDC_SUCCESS; + break; /* GOOD */ + case SAFE_PATH_UNTRUSTED: + /* Perms are wrong */ + oidc_errno = OIDC_EPERM; + break; /* perm error */ + case SAFE_PATH_TRUSTED: + case SAFE_PATH_TRUSTED_STICKY_DIR: + /* TRUSTED-only is fine */ + oidc_errno = OIDC_SUCCESS; + break; + case SAFE_PATH_ERROR: /* checking failed */ + default: /* Unknown state, should not be reached */ + oidc_errno = OIDC_EERROR; + oidc_seterror("unknown socket path checking error"); + break; + } + + /* free the range lists */ + safe_destroy_id_range_list(&ulist); + safe_destroy_id_range_list(&glist); + + return oidc_errno; +} \ No newline at end of file diff --git a/src/utils/file_io/safefile/check_file_path.h b/src/utils/file_io/safefile/check_file_path.h new file mode 100644 index 00000000..02bc25d5 --- /dev/null +++ b/src/utils/file_io/safefile/check_file_path.h @@ -0,0 +1,8 @@ +#ifndef OIDC_SAFEFILE_CHECK_FILE_PATH_H +#define OIDC_SAFEFILE_CHECK_FILE_PATH_H + +#include "utils/oidc_error.h" + +oidc_error_t check_socket_path(const char* path, const char* group); + +#endif // OIDC_SAFEFILE_CHECK_FILE_PATH_H diff --git a/src/utils/file_io/safefile/safe_id_range_list.c b/src/utils/file_io/safefile/safe_id_range_list.c new file mode 100644 index 00000000..5a8029de --- /dev/null +++ b/src/utils/file_io/safefile/safe_id_range_list.c @@ -0,0 +1,988 @@ +/* + * safefile package http://www.cs.wisc.edu/~kupsch/safefile + * + * Copyright 2007-2008, 2010-2011 James A. Kupsch + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#include + +#include "safe_id_range_list.h" + + + +/*********************************************************************** + * + * Initialize global variables + * + ***********************************************************************/ + +const id_t safe_err_id = (id_t)-1; + + +/*********************************************************************** + * + * Initialize global variables + * + ***********************************************************************/ + +typedef struct safe_id_range_list_elem +{ + id_t min_value; + id_t max_value; +} safe_id_range_list_elem; + + +/*********************************************************************** + * + * Initialize global constants + * + ***********************************************************************/ + +/** + * We need some trickery to get the maximum id_t: + * POSIX does not specify directly whether it is signed or unsigned. Since it + * should contain a (signed) pid_t and a uid_t and gid_t, and following from + * setreuid() it appears both id_t and uid_t/gid_t should be signed. That's not + * the case on all platforms... + * Solaris: uid_t/gid_t unsigned, id_t signed + * MacOS: uid_t/gid_t unsigned, id_t unsigned + * Linux: uid_t/gid_t unsigned, id_t unsigned */ + +/* Maximum of a signed type: all bits except highest are set */ +#define MAX_OF_TYPE_SIGNED(type) ((type)(~((type)1<<(8*sizeof(type)-1)))) + +/* Maximum of an unsigned type: all bits are set */ +#define MAX_OF_TYPE_UNSIGNED(type) ((type)(~(type)0)) + +/* Whether type is unsigned: -1 casted to it is positive */ +#define IS_UNSIGNED(type) ((type)(-1)>0) + +/* Maximum of type */ +#define MAX_OF_TYPE(type) \ + ( IS_UNSIGNED(type) ? MAX_OF_TYPE_UNSIGNED(type) \ + : MAX_OF_TYPE_SIGNED(type) ) + +/* maximum uid_t that fits in both a uid_t and an id_t */ +const uid_t safe_max_uid_t= + sizeof(id_t)>sizeof(uid_t) || + (sizeof(id_t)==sizeof(uid_t) && IS_UNSIGNED(id_t)) + ? MAX_OF_TYPE(uid_t) : (uid_t)(MAX_OF_TYPE(id_t)); + +/* maximum gid_t that fits in both a gid_t and an id_t */ +const gid_t safe_max_gid_t= + sizeof(id_t)>sizeof(gid_t) || + (sizeof(id_t)==sizeof(gid_t) && IS_UNSIGNED(id_t)) + ? MAX_OF_TYPE(gid_t) : (gid_t)(MAX_OF_TYPE(id_t)); + +/*********************************************************************** + * + * Functions for manipulating id range lists + * + ***********************************************************************/ + + +/* + * safe_init_id_range_list + * Initialize an id_range_list structure. + * parameters + * list + * pointer to an id range list + * returns + * 0 for success + * -1 on failure (errno == ENOMEM or EINVAL) + */ +int safe_init_id_range_list(safe_id_range_list *list) +{ + if (list == NULL) { + errno = EINVAL; + return -1; + } + + list->count = 0; + list->capacity = 10; + list->list = (safe_id_range_list_elem *)malloc(list->capacity * sizeof(list->list[0])); + if (list->list == 0) { + errno = ENOMEM; + return -1; + } + + return 0; +} + + +/* + * safe_add_id_range_to_list + * Adds a range of ids (all the ids between min_id and max_id inclusive) + * to the id_range_list. + * parameters + * list + * pointer to an id range list + * min_id + * the minimum id of the range to add + * max_id + * the maximum id of the range to add + * returns + * 0 for success + * -1 on failure (errno == ENOMEM or EINVAL) + */ +int safe_add_id_range_to_list(safe_id_range_list *list, id_t min_id, id_t max_id) +{ + if (list == NULL || min_id > max_id) { + errno = EINVAL; + return -1; + } + + if (list->count == list->capacity) { + size_t new_capacity = 10 + 11 * list->capacity / 10; + safe_id_range_list_elem *new_list = (safe_id_range_list_elem *)malloc(new_capacity * sizeof(new_list[0])); + if (new_list == 0) { + errno = ENOMEM; + return -1; + } + memcpy(new_list, list->list, list->count * sizeof(new_list[0])); + free(list->list); + list->list = new_list; + list->capacity = new_capacity; + } + + list->list[list->count].min_value = min_id; + list->list[list->count++].max_value = max_id; + + return 0; +} + + +/* + * safe_add_id_to_list + * Add the single id to the list. This is the same as calling + * safe_add_id_range_to_list with min_id and max_id set to id. + * parameters + * list + * pointer to an id range list + * id + * the id to add + * returns + * 0 for success + * -1 on failure (errno == ENOMEM) + */ +int safe_add_id_to_list(safe_id_range_list *list, id_t id) +{ + return safe_add_id_range_to_list(list, id, id); +} + + +/* + * safe_destroy_id_range_list + * Destroy a id_range_list, including any memory have acquired. + * parameters + * list + * pointer to id range list structure to destroy + * returns + * nothing + */ +void safe_destroy_id_range_list(safe_id_range_list *list) +{ + if (list == NULL) { + errno = EINVAL; + return; + } + + list->capacity = 0; + list->count = 0; + free(list->list); + list->list = 0; +} + + +/* + * safe_is_id_in_list + * Check if the id is in one of the id ranges in the id range list. + * parameters + * list + * pointer to an id range list + * id + * the id to check + * returns + * 1 id is in the list + * 0 id is not in the list + * -1 the list is NULL + */ +int safe_is_id_in_list(safe_id_range_list *list, id_t id) +{ + size_t i; + + if (list == NULL) { + errno = EINVAL; + return -1; + } + + for (i = 0; i < list->count; ++i) { + if (list->list[i].min_value <= id && id <= list->list[i].max_value) { + return 1; + } + } + + return 0; +} + + +/* + * safe_is_id_list_empty + * Returns true if the id_range_list contains 0 ranges. + * parameters + * list + * pointer to an id range list + * returns + * 1 id is in the list + * 0 id is not in the list + * -1 the list is NULL + */ +int safe_is_id_list_empty(safe_id_range_list *list) { + if (list == NULL) { + errno = EINVAL; + return -1; + } + + return (list->count == 0); +} + + + + + +/*********************************************************************** + * + * Functions for parsing ids, id ranges and id lists of numbers, uids and gids + * + ***********************************************************************/ + + +/* + * skip_whitespace_const + * Returns a pointer to the first non-whitespace character in the + * const string s. + * parameters + * s + * the string to skip whitespace + * returns + * location of first non-whitespace + */ +static const char *skip_whitespace_const(const char *s) +{ + while (*s && isspace((unsigned char)*s)) { + ++s; + } + + return s; +} + + +/* + * name_to_error + * Always return the err id (-1) and set errno to EINVAL. + * parameters + * name + * unused + * returns + * safe_err_id and errno = EINVAL + */ +static id_t name_to_error(const char *name) +{ + (void)name; + errno = EINVAL; + return safe_err_id; +} + + +/* + * name_to_uid + * Return the uid matching the name if it exists. If the name does + * not exist or there was an error in getpwnam, safe_err_id is + * returned and errno is set to a non-0 value. If getpwnam fails + * with errno unchanged, errno is set to EINVAL. If the uid is larger than + * the largest id_t and errno is unset, errno is set to EOVERFLOW. + * parameters + * name + * user name to lookup + * returns + * The uid corresponding to name if it exists. + * safe_err_id if the name does not exist or an error occurs (errno is + * set to the value from getpwnam or EINVAL if getpwnam does not set it or + * EOVERFLOW if the uid is larger than the largest id_t). + */ +static id_t name_to_uid(const char *name) +{ + struct passwd *pw = getpwnam(name); + + errno = 0; + + if (!pw) { + if (errno == 0) { + errno = EINVAL; + } + return safe_err_id; + } + + if (pw->pw_uid > safe_max_uid_t) { + if (errno == 0) { + errno = EOVERFLOW; + } + return safe_err_id; + } + + return (id_t)(pw->pw_uid); +} + + +/* + * name_to_gid + * Return the uid matching the name if it exists. If the name does + * not exist or there was an error in getgrnam, safe_err_id is + * returned and errno is set to a non-0 value. If getgrnam fails + * with errno unchanged, errno is set to EINVAL. If the gid is larger than + * the largest id_t and errno is unset, errno is set to EOVERFLOW. + * parameters + * name + * group name to lookup + * returns + * The uid corresponding to name if it exists. + * safe_err_id if the name does not exist or an error occurs (errno is + * set to the value from getgrnam or EINVAL if getgrnam does not set it or + * EOVERFLOW if the gid is larger than the largest id_t). + */ +static id_t name_to_gid(const char *name) +{ + struct group *gr = getgrnam(name); + + errno = 0; + + if (!gr) { + if (errno == 0) { + errno = EINVAL; + } + return safe_err_id; + } + + if (gr->gr_gid > safe_max_gid_t) { + if (errno == 0) { + errno = EOVERFLOW; + } + return safe_err_id; + } + return (id_t)(gr->gr_gid); +} + + +/* + * strto_id + * Return the id corresponding to the longest sequence of characters + * that could form an unsigned integer or a name after skipping leading + * whitespace. An unsigned integer begins with 0-9 and continues until a + * non-digit character. A name starts with a non-digit character and + * continues until a whitespace, end of string or a colon. + * + * The numeric form is converted to an unsigned integer and the name + * form is converted to an unsigned integer using the lookup func. + * + * If endptr is not NULL *endptr is set to point to the character one past + * the end of the parsed value, just as strtoul() does. + * + * If there is nothing valid to try to convert to an id_t, then *endptr is + * set to value. This can only occur if value consists only of whitespace + * characters. + * + * On error errno is set to a non-zero value including EINVAL and ERANGE. + * On success errno is set to 0. + * parameters + * id + * a pointer to store the id converted to a id_t + * value + * pointer to the beginning of the string to find the name or + * number + * endptr + * a pointer to one character past the end the sequence used to, + * or NULL if the value is not needed + * lookup + * function pointer to convert a name to an id + * returns + * nothing, but sets *id, *endptr, and errno + */ +typedef id_t (*lookup_func)(const char *); + +static int strto_id(id_t *id, const char *value, const char **endptr, lookup_func lookup) +{ + const char *endp; + const char *id_begin; + + if (id == NULL || value == NULL || lookup == NULL) { + errno = EINVAL; + if (id) { + *id = safe_err_id; + } + return -1; + } + + endp = value; + + id_begin = skip_whitespace_const(value); + + errno = 0; + + if (isdigit((unsigned char)*id_begin)) { + /* is numeric form, parse as a number */ + char *e; + *id = (id_t)strtoul(id_begin, &e, 10); + endp = e; + } else if (*id_begin) { + /* is not numeric, parse as a name using lookup function */ + char *id_name; + size_t id_len; + char small_buf[16]; /* should be big enough to hold most names */ + + /* find end - name can contain anything except whitespace and colons */ + endp = id_begin; + while (*endp && !isspace((unsigned char)*endp) && *endp != ':') { + ++endp; + } + + id_len = (size_t)(endp - id_begin); + + if (id_len == 0) { + errno = EINVAL; + *id = safe_err_id; + if (endptr) { + *endptr = endp; + } + return -1; + } else if (id_len < sizeof(small_buf)) { + /* use small_buf as the id fits */ + id_name = small_buf; + } else { + /* malloc a buffer as id is too large for small_buf */ + id_name = (char *)malloc(id_len + 1); + if (id_name == NULL) { + errno = ENOMEM; + *id = safe_err_id; + if (endptr) { + *endptr = endp; + } + return -1; + } + } + + /* copy the id to the buffer */ + memcpy(id_name, id_begin, id_len); + id_name[id_len] = '\0'; + + *id = lookup(id_name); + + /* free buffer if malloc'd */ + if (id_name != small_buf) { + free(id_name); + } + } else { + /* value contains nothing parsable */ + *id = safe_err_id; + errno = EINVAL; + } + + if (endptr) { + *endptr = endp; + } + + return 0; +} + + +/* + * safe_strto_uid + * Parse the string value and return the user id of the first id in the + * string as a uid_t. This follows the same rules as strto_id with + * non-numeric ids converted to the matching user id. + * + * On error errno is set to a non-zero value including EINVAL and ERANGE. + * On success errno is set to 0. + * parameters + * value + * pointer to the beginning of the string to find the name or + * number + * endptr + * a pointer to one character past the end the sequence used to, + * or NULL if the value is not needed + * returns + * the user id, also updates *endptr and errno + */ +uid_t safe_strto_uid(const char *value, const char **endptr) +{ + id_t id; + + strto_id(&id, value, endptr, name_to_uid); + + return (uid_t)id; +} + + +/* + * safe_strto_gid + * Parse the string value and return the group id of the first id in the + * string as gid_t. This follows the same rules as strto_id with + * non-numeric ids converted to the matching group id. + * + * On error errno is set to a non-zero value including EINVAL and ERANGE. + * On success errno is set to 0. + * parameters + * value + * pointer to the beginning of the string to find the name or + * number + * endptr + * a pointer to one character past the end the sequence used to, + * or NULL if the value is not needed + * returns + * the group id, also updates *endptr and errno + */ +gid_t safe_strto_gid(const char *value, const char **endptr) +{ + id_t id; + + strto_id(&id, value, endptr, name_to_gid); + + return (gid_t)id; +} + + +/* + * safe_strto_id + * Parse the string value and return the the first number in the string as + * an id_t. This follows the same rules as strto_id with non-numeric ids + * returning an error. + * + * On error errno is set to a non-zero value including EINVAL and ERANGE. + * On success errno is set to 0. + * parameters + * value + * pointer to the beginning of the string to find the name or + * number + * endptr + * a pointer to one character past the end the sequence used to, + * or NULL if the value is not needed + * returns + * the group id, also updates *endptr and errno + */ +id_t safe_strto_id(const char *value, const char **endptr) +{ + id_t id; + + strto_id(&id, value, endptr, name_to_error); + + return id; +} + + +/* + * strto_id_range + * Returns a pair of id's denoting a range of ids. The form of the string + * must be * [ * '-' * ( | '*' ) ]? + * + * is of the form parsed by strto_id. If the option '-' and second + * is not present, the first is returned for both the minimum + * and maximum value. Since an in a non-numeric form may contain a + * '-', a space must precede the '-' if the first is in a non-numeric + * form. The value '*' as the second value specifies the maximum allowed + * id (assumes id_t is an unsigned type, if it is unsigned the code will + * work correctly, but '*' will not work. + * + * It is an error if min_id is greater than max_id. + * + * If endptr is not NULL *endptr is set to point to the character one past + * the end of the parsed value, just as strtoul() does. + * + * On error errno is set to a non-zero value including EINVAL and ERANGE. + * On success errno is set to 0. + * parameters + * min_id + * a pointer to store the minimum id converted to a id_t + * max_id + * a pointer to store the maximum id converted to a id_t + * value + * pointer to the beginning of the string to find the name or + * number + * endptr + * a pointer to one character past the end the sequence used to, + * or NULL if the value is not needed + * lookup + * function pointer to convert a name to an id + * max_id_t + * maximum value for underlying type of id_t, which fits in both + * id_t and the underlying type + * returns + * nothing, but sets *min_id, *max_id, *endptr, and errno + */ +static void strto_id_range(id_t *min_id, id_t *max_id, const char *value, const char **endptr, lookup_func lookup, id_t max_id_t) +{ + const char *endp; + + strto_id(min_id, value, &endp, lookup); + if (errno == 0 && value != endp) { + /* parsed min correctly, check for a '-' and max value */ + value = skip_whitespace_const(endp); + if (*value == '-') { + ++value; + value = skip_whitespace_const(value); + if (*value == '*') { + /* Make sure to use the proper maximum, signed or unsigned and + * corresponding to the correct underlying type, uid_t, gid_t + * pid_t etc. */ + *max_id = max_id_t; + endp = value + 1; + } else { + strto_id(max_id, value, &endp, lookup); + } + } else { + *max_id = *min_id; + } + } else { + *max_id = *min_id; + } + + if (endptr) { + *endptr = endp; + } + + if (*min_id > *max_id) { + errno = EINVAL; + } +} + + +/* + * strto_id_list + * Adds the ranges in the value to the list. Ranges are as specified in + * strto_id_range, and there may be multiple ranges in value that are + * separated by whitespace and a colon of the form: + * [ * ':' * ]* + * + * Each range is of the form required by strto_id_range. + * + * It is an error if any of the ranges contain an error. + * + * If endptr is not NULL *endptr is set to point to the character one past + * the end of the parsed value, just as strtoul() does. + * + * On error errno is set to a non-zero value including EINVAL and ERANGE. + * On success errno is set to 0. + * parameters + * list + * a pointer to a range list to have the ranges parsed added + * value + * pointer to the beginning of the string to find the name or + * number + * endptr + * a pointer to one character past the end the sequence used to, + * or NULL if the value is not needed + * lookup + * function pointer to convert a name to an id + * max_id_t + * maximum value for underlying type of id_t, which fits in both + * id_t and the underlying type + * returns + * nothing, but adds entries to *list, and sets *endptr, and errno + */ +static void strto_id_list(safe_id_range_list *list, const char *value, const char **endptr, lookup_func lookup, id_t max_id_t) +{ + const char * endp = value; + + if (list == NULL || value == NULL) { + errno = EINVAL; + if (endptr) { + *endptr = value; + } + return; + } + + while (1) { + id_t min_id; + id_t max_id; + + strto_id_range(&min_id, &max_id, value, &endp, lookup, max_id_t); + if (errno != 0 || value == endp) { + break; + } + + safe_add_id_range_to_list(list, min_id, max_id); + + value = skip_whitespace_const(endp); + if (*value == ':') { + ++value; + } else { + break; + } + } + + if (endptr) { + *endptr = endp; + } +} + + +/* + * safe_strto_id_list + * Parse the value and store the ranges in the range list in the list + * structure. Non-numeric ids are treated as errors. + * + * The value is parsed as described in strto_id_list. + * + * It is an error if any of the ranges contain an error. + * + * If endptr is not NULL *endptr is set to point to the character one past + * the end of the parsed value, just as strtoul() does. + * + * On error errno is set to a non-zero value including EINVAL and ERANGE. + * On success errno is set to 0. + * parameters + * list + * a pointer to a range list to have the ranges parsed added + * value + * pointer to the beginning of the string to find the name or + * number + * endptr + * a pointer to one character past the end the sequence used to, + * or NULL if the value is not needed + * returns + * nothing, but adds entries to *list, and sets *endptr, and errno + */ +void safe_strto_id_list(safe_id_range_list *list, const char *value, const char **endptr) +{ + /* Note: only non-ranges are allowed: so can safely give 0 as maximum */ + strto_id_list(list, value, endptr, name_to_error, 0); +} + + +/* + * safe_parse_uid_list + * Parse the value and store the ranges in the range list in the list + * structure. Non-numeric ids are converted to ids by looking the + * name as a username and returning its uid. + * + * The value is parsed as described in strto_id_list. + * + * It is an error if any of the ranges contain an error. + * + * If endptr is not NULL *endptr is set to point to the character one past + * the end of the parsed value, just as strtoul() does. + * + * On error errno is set to a non-zero value including EINVAL and ERANGE. + * On success errno is set to 0. + * parameters + * list + * a pointer to a range list to have the ranges parsed added + * value + * pointer to the beginning of the string to find the name or + * number + * endptr + * a pointer to one character past the end the sequence used to, + * or NULL if the value is not needed + * returns + * nothing, but adds entries to *list, and sets *endptr, and errno + */ +void safe_strto_uid_list(safe_id_range_list *list, const char *value, const char **endptr) +{ + strto_id_list(list, value, endptr, name_to_uid, (id_t)safe_max_uid_t); +} + + +/* + * safe_parse_gid_list + * Parse the value and store the ranges in the range list in the list + * structure. Non-numeric ids are converted to ids by looking the + * name as a group name and returning its gid. + * + * The value is parsed as described in strto_id_list. + * + * It is an error if any of the ranges contain an error. + * + * If endptr is not NULL *endptr is set to point to the character one past + * the end of the parsed value, just as strtoul() does. + * + * On error errno is set to a non-zero value including EINVAL and ERANGE. + * On success errno is set to 0. + * parameters + * list + * a pointer to a range list to have the ranges parsed added + * value + * pointer to the beginning of the string to find the name or + * number + * endptr + * a pointer to one character past the end the sequence used to, + * or NULL if the value is not needed + * returns + * nothing, but adds entries to *list, and sets *endptr, and errno + */ +void safe_strto_gid_list(safe_id_range_list *list, const char *value, const char **endptr) +{ + strto_id_list(list, value, endptr, name_to_gid, (id_t)safe_max_uid_t); +} + + +/* + * parse_id_list + * Parse the value and store the ranges in the range list in the list + * structure. Non-numeric ids are converted to ids by looking the + * name as a group name and returning its gid. + * + * The value is parsed as described in strto_id_list. + * + * It is an error if any of the ranges contain an error. + * + * It is an error if there is non-whitespace after the parsed value. + * + * On error errno is set to a non-zero value including EINVAL and ERANGE. + * On success errno is set to 0. + * parameters + * list + * a pointer to a range list to have the ranges parsed added + * value + * pointer to the beginning of the string to find the name or + * number + * returns + * 0 on success + * -1 on error + */ +static int parse_id_list(safe_id_range_list *list, const char *value, lookup_func lookup, id_t max_id_t) +{ + const char *endp; + + strto_id_list(list, value, &endp, lookup, max_id_t); + + if (errno != 0) { + return -1; + } + + /* check if there is non-whitespace after the parse portion of value */ + endp = skip_whitespace_const(endp); + if (*endp != '\0') { + return -1; + } + + return 0; +} + + +/* + * safe_parse_id_list + * Parse the value and store the ranges in the range list in the list + * structure. + * + * The value is parsed as described in strto_id_list. + * + * It is an error if any of the ranges contain an error. + * + * It is an error if there is non-whitespace after the parsed value. + * + * On error errno is set to a non-zero value including EINVAL and ERANGE. + * On success errno is set to 0. + * parameters + * list + * a pointer to a range list to have the ranges parsed added + * value + * pointer to the beginning of the string to find the name or + * number + * returns + * 0 on success + * -1 on error + */ +int safe_parse_id_list(safe_id_range_list *list, const char *value) +{ + /* Note: only non-ranges are allowed: so can safely give 0 as maximum */ + return parse_id_list(list, value, name_to_error, 0); +} + + +/* + * safe_parse_uid_list + * Parse the value and store the ranges in the range list in the list + * structure. Non-numeric ids are converted to ids by looking the + * name as a user name and returning its uid. + * + * The value is parsed as described in strto_id_list. + * + * It is an error if any of the ranges contain an error. + * + * It is an error if there is non-whitespace after the parsed value. + * + * On error errno is set to a non-zero value including EINVAL and ERANGE. + * On success errno is set to 0. + * parameters + * list + * a pointer to a range list to have the ranges parsed added + * value + * pointer to the beginning of the string to find the name or + * number + * returns + * 0 on success + * -1 on error + */ +int safe_parse_uid_list(safe_id_range_list *list, const char *value) +{ + return parse_id_list(list, value, name_to_uid, (id_t)safe_max_uid_t); +} + + +/* + * safe_parse_gid_list + * Parse the value and store the ranges in the range list in the list + * structure. Non-numeric ids are converted to ids by looking the + * name as a group name and returning its gid. + * + * The value is parsed as described in strto_id_list. + * + * It is an error if any of the ranges contain an error. + * + * It is an error if there is non-whitespace after the parsed value. + * + * On error errno is set to a non-zero value including EINVAL and ERANGE. + * On success errno is set to 0. + * parameters + * list + * a pointer to a range list to have the ranges parsed added + * value + * pointer to the beginning of the string to find the name or + * number + * returns + * 0 on success + * -1 on error + */ +int safe_parse_gid_list(safe_id_range_list *list, const char *value) +{ + return parse_id_list(list, value, name_to_gid, (id_t)safe_max_gid_t); +} diff --git a/src/utils/file_io/safefile/safe_id_range_list.h b/src/utils/file_io/safefile/safe_id_range_list.h new file mode 100644 index 00000000..b5c49e66 --- /dev/null +++ b/src/utils/file_io/safefile/safe_id_range_list.h @@ -0,0 +1,86 @@ +/* safe_id_range_list.h. Generated by configure. */ +#ifndef SAFE_ID_RANGE_LIST_H_ +#define SAFE_ID_RANGE_LIST_H_ + +/* + * safefile package http://www.cs.wisc.edu/~kupsch/safefile + * + * Copyright 2007-2008, 2011 James A. Kupsch + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +#include +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + + +/* define id_t to uid_t if not defined */ +/* #undef id_t */ + +#ifndef id_t +#define id_t uid_t +#endif + +/* maximum uid_t, taking into account sign of id_t and uid_t */ +extern const uid_t safe_max_uid_t; + +/* maximum gid_t, taking into account sign of id_t and gid_t */ +extern const gid_t safe_max_gid_t; + + +/* id used when an error occurs */ +extern const id_t safe_err_id; + + +/* declare implementation data structure type */ +struct safe_id_range_list_elem; + + +typedef struct safe_id_range_list +{ + size_t count; + size_t capacity; + struct safe_id_range_list_elem *list; +} safe_id_range_list; + + +int safe_init_id_range_list(safe_id_range_list *list); +int safe_add_id_range_to_list(safe_id_range_list *list, id_t min_id, id_t max_id); +int safe_add_id_to_list(safe_id_range_list *list, id_t id); +void safe_destroy_id_range_list(safe_id_range_list *list); +int safe_is_id_in_list(safe_id_range_list *list, id_t id); +int safe_is_id_list_empty(safe_id_range_list *list); + +uid_t safe_strto_uid(const char *value, const char **endptr); +gid_t safe_strto_gid(const char *value, const char **endptr); +id_t safe_strto_id(const char *value, const char **endptr); +void safe_strto_id_list(safe_id_range_list *list, const char *value, const char **endptr); +void safe_strto_uid_list(safe_id_range_list *list, const char *value, const char **endptr); +void safe_strto_gid_list(safe_id_range_list *list, const char *value, const char **endptr); +int safe_parse_id_list(safe_id_range_list *list, const char *value); +int safe_parse_uid_list(safe_id_range_list *list, const char *value); +int safe_parse_gid_list(safe_id_range_list *list, const char *value); + + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/src/utils/file_io/safefile/safe_is_path_trusted.c b/src/utils/file_io/safefile/safe_is_path_trusted.c new file mode 100644 index 00000000..4b024fb0 --- /dev/null +++ b/src/utils/file_io/safefile/safe_is_path_trusted.c @@ -0,0 +1,1354 @@ +/* + * safefile package http://www.cs.wisc.edu/~kupsch/safefile + * + * Copyright 2007-2008, 2010-2011 James A. Kupsch + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#define _XOPEN_SOURCE 500 + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "safe_id_range_list.h" +#include "safe_is_path_trusted.h" + + + +/*********************************************************************** + * + * Functions to check the safety of the directory or path + * + ***********************************************************************/ + + +/* + * is_mode_trusted + * Returns trustedness of mode + * parameters + * stat_buf + * the result of the stat system call on the entry in question + * safe_uids + * list of safe user ids + * safe_gids + * list of safe group ids + * returns + * -1 SAFE_PATH_ERROR + * 0 SAFE_PATH_UNTRUSTED + * 1 SAFE_PATH_TRUSTED_STICKY_DIR + * 2 SAFE_PATH_TRUSTED + * 3 SAFE_PATH_TRUSTED_CONFIDENTIAL + */ +static int is_mode_trusted(struct stat *stat_buf, safe_id_range_list *trusted_uids, safe_id_range_list *trusted_gids) +{ + mode_t mode = stat_buf->st_mode; + uid_t uid = stat_buf->st_uid; + gid_t gid = stat_buf->st_gid; + int uid_in_list; + int gid_in_list; + + /* safe_is_id_in_list() needs id_t, which isn't always of the same + * signed-ness as uid_t and gid_t (e.g. Solaris) */ + if (uid>safe_max_uid_t || gid>safe_max_gid_t) { + return SAFE_PATH_ERROR; + } + + uid_in_list = safe_is_id_in_list(trusted_uids, (id_t)uid); + gid_in_list = safe_is_id_in_list(trusted_gids, (id_t)gid); + + + if (uid_in_list == -1 || gid_in_list == -1) { + return SAFE_PATH_ERROR; + } else { + int is_trusted_uid = uid == 0 || uid_in_list; + int is_dir = S_ISDIR(mode); + int is_untrusted_gid = !gid_in_list; + int is_untrusted_gid_writable + = is_untrusted_gid && (mode & S_IWGRP); + mode_t is_other_writable = (mode & S_IWOTH); + + int is_trusted = SAFE_PATH_UNTRUSTED; + + if (is_trusted_uid && !is_untrusted_gid_writable && !is_other_writable) { + /* Path is trusted, now determine if it is confidential. + * Do not allow viewing of directory entries or access to any of the + * entries of a directory. For other types do not allow read access. + */ + mode_t gid_read_mask = (mode_t)(is_dir ? (S_IXGRP | S_IRGRP) : S_IRGRP); + int is_untrusted_gid_readable = is_untrusted_gid && (mode & gid_read_mask); + + mode_t other_read_mask = (mode_t)(is_dir ? (S_IXOTH | S_IROTH) : S_IROTH); + mode_t is_other_readable = (mode & other_read_mask); + + if (is_other_readable || is_untrusted_gid_readable) { + is_trusted = SAFE_PATH_TRUSTED; + } else { + is_trusted = SAFE_PATH_TRUSTED_CONFIDENTIAL; + } + } else if (S_ISLNK(mode)) { + /* Symbolic links are trusted since they are immutable */ + is_trusted = SAFE_PATH_TRUSTED; + } else { + /* Directory with sticky bit set and trusted owner is trusted */ + int is_sticky_dir = is_dir && (mode & S_ISVTX); + + if (is_sticky_dir && is_trusted_uid) { + is_trusted = SAFE_PATH_TRUSTED_STICKY_DIR; + } + } + + return is_trusted; + } +} + + +/* abbreviations to make trust_matrix initialization easier to read */ +enum { PATH_U = SAFE_PATH_UNTRUSTED, + PATH_S = SAFE_PATH_TRUSTED_STICKY_DIR, + PATH_T = SAFE_PATH_TRUSTED, + PATH_C = SAFE_PATH_TRUSTED_CONFIDENTIAL +}; + + +/* trust composition table, given the trust of the parent directory and the child + * this is only valid for directories. is_component_in_dir_trusted() modifies it + * slightly for other file system object types + */ +static int trust_matrix[][4] = { + /* parent\child | PATH_U PATH_S PATH_T PATH_C */ + /* ------ ------------------------------ */ + /* PATH_U */ { PATH_U, PATH_U, PATH_U, PATH_U }, + /* PATH_S */ { PATH_U, PATH_S, PATH_T, PATH_C }, + /* PATH_T */ { PATH_U, PATH_S, PATH_T, PATH_C }, + /* PATH_C */ { PATH_U, PATH_S, PATH_T, PATH_C } +}; + + +/* + * is_component_in_dir_trusted + * Returns trustedness of mode. See trust_matrix above, plus if the + * parent directory is a stick bit directory every file system object + * that can be hard linked (all except directories) is SAFE_PATH_UNTRUSTED. + * parameters + * parent_dir_trust + * trust level of parent directory + * child_stat_buf + * the result of the stat system call on the entry in question + * safe_uids + * list of safe user ids + * safe_gids + * list of safe group ids + * returns + * -1 SAFE_PATH_ERROR + * 0 SAFE_PATH_UNTRUSTED + * 1 SAFE_PATH_TRUSTED_STICKY_DIR + * 2 SAFE_PATH_TRUSTED + * 3 SAFE_PATH_TRUSTED_CONFIDENTIAL + */ +static int is_component_in_dir_trusted( + int parent_dir_trust, + struct stat *child_stat_buf, + safe_id_range_list *trusted_uids, + safe_id_range_list *trusted_gids + ) +{ + int child_trust = is_mode_trusted(child_stat_buf, trusted_uids, trusted_gids); + + if (child_trust == SAFE_PATH_ERROR) { + return child_trust; + } else { + int status = trust_matrix[parent_dir_trust][child_trust]; + + /* Fix trust of objects in a sticky bit directory. Everything in them + * should be considered untrusted except directories. Any user can + * create a hard link to any file system object except a directory. + * Although the contents of the object may be trusted based on the + * permissions, the directory entry itself could have been made by an + * untrusted user. + */ + int is_dir = S_ISDIR(child_stat_buf->st_mode); + if (parent_dir_trust == SAFE_PATH_TRUSTED_STICKY_DIR && !is_dir) { + status = SAFE_PATH_UNTRUSTED; + } + + return status; + } +} + + +/* + * is_current_working_directory_trusted + * Returns the trustedness of the current working directory. If any + * directory from here to the root is untrusted the path is untrusted, + * otherwise it returns the trustedness of the current working directory. + * + * This function is not thread safe. The current working directory is + * changed while checking the path, and restores it on exit. + * parameters + * safe_uids + * list of safe user ids + * safe_gids + * list of safe group ids + * returns + * -1 SAFE_PATH_ERROR + * 0 SAFE_PATH_UNTRUSTED + * 1 SAFE_PATH_TRUSTED_STICKY_DIR + * 2 SAFE_PATH_TRUSTED + * 3 SAFE_PATH_TRUSTED_CONFIDENTIAL + */ +static int is_current_working_directory_trusted(safe_id_range_list *trusted_uids, safe_id_range_list *trusted_gids) +{ + int saved_dir = -1; + int parent_dir_fd = -1; + int r; + int status = SAFE_PATH_UNTRUSTED; /* trust of cwd or error value */ + int cur_status; /* trust of current directory being checked */ + struct stat cur_stat; + struct stat prev_stat; + int not_at_root; + + + /* save the cwd, so it can be restored */ + saved_dir = open(".", O_RDONLY); + if (saved_dir == -1) { + status = SAFE_PATH_ERROR; + goto restore_dir_and_exit; + } + + r = fstat(saved_dir, &cur_stat); + if (r == -1) { + status = SAFE_PATH_ERROR; + goto restore_dir_and_exit; + } + + + /* Walk the directory tree, from the current working directory to the root. + * + * If there is a directory that is_trusted_mode returns SAFE_PATH_UNTRUSTED + * exit immediately with that value + * + * Assumes no hard links to directories. + */ + do { + cur_status = is_mode_trusted(&cur_stat, trusted_uids, trusted_gids); + if (cur_status <= SAFE_PATH_UNTRUSTED) { + /* untrusted directory permissions or an error occurred */ + status = cur_status; + goto restore_dir_and_exit; + } + + if (status == SAFE_PATH_UNTRUSTED) { + /* This is true only the first time through the loop. The + * directory is the current work directory (cwd). If the rest of + * the path to the root is trusted, the trust of the cwd is the + * value returned by this function. + */ + status = cur_status; + } + + prev_stat = cur_stat; + + /* get handle to parent directory */ + parent_dir_fd = open("..", O_RDONLY); + if (parent_dir_fd == -1) { + status = SAFE_PATH_ERROR; + goto restore_dir_and_exit; + } + + /* get the parent directory stat buffer */ + r = fstat(parent_dir_fd, &cur_stat); + if (r == -1) { + status = SAFE_PATH_ERROR; + goto restore_dir_and_exit; + } + + /* check if we are at the root directory (parent of root is root) */ + not_at_root = cur_stat.st_dev != prev_stat.st_dev || cur_stat.st_ino != prev_stat.st_ino; + + if (not_at_root) { + /* not at root, change directory to parent */ + r = fchdir(parent_dir_fd); + if (r == -1) { + status = SAFE_PATH_ERROR; + goto restore_dir_and_exit; + } + } + + /* done with parent directory handle, close it */ + r = close(parent_dir_fd); + if (r == -1) { + status = SAFE_PATH_ERROR; + goto restore_dir_and_exit; + } + + parent_dir_fd = -1; + } while (not_at_root); + + + restore_dir_and_exit: + + /* restore cwd, close open file descriptors, return value */ + if (saved_dir != -1) { + r = fchdir(saved_dir); + if (r == -1) { + status = SAFE_PATH_ERROR; + } + r = close(saved_dir); + if (r == -1) { + status = SAFE_PATH_ERROR; + } + } + if (parent_dir_fd != -1) { + r = close(parent_dir_fd); + if (r == -1) { + status = SAFE_PATH_ERROR; + } + } + + return status; +} + + + +/* + * is_current_working_directory_trusted_r + * Returns the trustedness of the current working directory. If any + * directory from here to the root is untrusted the path is untrusted, + * otherwise it returns the trustedness of the current working directory. + * parameters + * safe_uids + * list of safe user ids + * safe_gids + * list of safe group ids + * returns + * -1 SAFE_PATH_ERROR + * 0 SAFE_PATH_UNTRUSTED + * 1 SAFE_PATH_TRUSTED_STICKY_DIR + * 2 SAFE_PATH_TRUSTED + * 3 SAFE_PATH_TRUSTED_CONFIDENTIAL + */ +static int is_current_working_directory_trusted_r(safe_id_range_list *trusted_uids, safe_id_range_list *trusted_gids) +{ + int r; + int status = SAFE_PATH_UNTRUSTED; /* trust of cwd or error value */ + int cur_status; /* trust of current directory being checked */ + struct stat cur_stat; + struct stat prev_stat; + int not_at_root; + char path[PATH_MAX] = "."; + char *path_end = &path[0]; /* points to null char, except on 1st pass */ + + + r = lstat(path, &cur_stat); + if (r == -1) { + return SAFE_PATH_ERROR; + } + + /* Walk the directory tree, from the current working directory to the root. + * + * If there is a directory that is_trusted_mode returns SAFE_PATH_UNTRUSTED + * or SAFE_PATH_ERROR, exit immediately with that value + * + * Assumes no hard links to directories. + */ + do { + cur_status = is_mode_trusted(&cur_stat, trusted_uids, trusted_gids); + if (cur_status <= SAFE_PATH_UNTRUSTED) { + /* untrusted directory permissions or an error occurred */ + return cur_status; + } + + if (status == SAFE_PATH_UNTRUSTED) { + /* This is true only the first time through the loop. The + * directory is the current work directory (cwd). If the rest of + * the path to the root is trusted, the trust of the cwd is the + * value returned by this function. + */ + status = cur_status; + } + + prev_stat = cur_stat; + + if (path_end != path) { + /* if not the first time through, append a directory separator */ + if ((size_t)(path_end - path + 1) >= sizeof(path)) { + /* couldn't add 1 character */ + errno = ENAMETOOLONG; + return SAFE_PATH_ERROR; + } + + *path_end++ = '/'; + *path_end = '\0'; + } + + /* append a parent directory, "..", or on the first pass set to ".." */ + if ((size_t)(path_end - path + 2) >= sizeof(path)) { + /* couldn't add 2 characters */ + errno = ENAMETOOLONG; + return SAFE_PATH_ERROR; + } + + *path_end++ = '.'; + *path_end++ = '.'; + *path_end = '\0'; + + /* get the parent directory stat buffer */ + r = lstat(path, &cur_stat); + if (r == -1) { + return SAFE_PATH_ERROR; + } + + /* check if we are at the root directory (parent of root is root) */ + not_at_root = cur_stat.st_dev != prev_stat.st_dev || cur_stat.st_ino != prev_stat.st_ino; + } while (not_at_root); + + return status; +} + + + +#ifdef SYMLOOP_MAX +#define MAX_SYMLINK_DEPTH SYMLOOP_MAX +#else +#define MAX_SYMLINK_DEPTH 32 +#endif + +typedef struct dir_stack { + struct dir_path { + char *original_ptr; /* points to beginning of malloc'd buffer */ + char *cur_position; /* points to next component in path */ + } stack[MAX_SYMLINK_DEPTH]; + + int count; /* number of items on stack */ +} dir_stack; + + + +/* + * init_dir_stack + * Initialize a dir_stack data structure + * parameters + * stack + * pointer to a dir_stack to initialize + * returns + * Nothing + */ +static void init_dir_stack(dir_stack *stack) +{ + stack->count = 0; +} + + +/* + * destroy_dir_stack + * Destroy a dir_stack data structure by freeing paths that have + * been pushed onto the stack + * parameters + * stack + * pointer to a dir_stack to destroy + * returns + * Nothing + */ +static void destroy_dir_stack(dir_stack *stack) +{ + while (stack->count > 0) { + free(stack->stack[--stack->count].original_ptr); + } +} + + +/* + * push_path_on_stack + * Pushes a copy of the path onto the directory stack + * parameters + * stack + * pointer to a dir_stack to get pushed + * path + * path to push on the stack. A copy is made. + * returns + * 0 on success + * <0 on error (if the stack if contains MAX_SYMLINK_DEPTH directories + * errno = ELOOP for detecting symbolic link loops + */ +static int push_path_on_stack(dir_stack *stack, const char *path) +{ + char *new_path; + + if (stack->count >= MAX_SYMLINK_DEPTH) { + /* return potential symbolic link loop */ + errno = ELOOP; + return -1; + } + + new_path = strdup(path); + if (new_path == NULL) { + return -1; + } + + stack->stack[stack->count].original_ptr = new_path; + stack->stack[stack->count].cur_position = new_path; + ++stack->count; + + return 0; +} + + +/* + * get_next_component + * Returns the next directory component that was pushed on the stack. + * This value is always a local entry in the current directory (contains + * no "/"), unless this is the first call to get_next_component after + * an absolute path name was pushed on the stack, in which case the root + * directory path ("/") is returned. + * + * The pointer to path is valid until the next call to get_next_component, + * or destroy_dir_stack is called. The dir_stack owns the memory pointed + * by *path. + * parameters + * stack + * pointer to a dir_stack to get the next component + * path + * pointer to a pointer to store the next component + * returns + * 0 on success + * <0 on stack empty + */ +static int get_next_component(dir_stack *stack, const char **path) +{ + while (stack->count > 0) { + if (!*stack->stack[stack->count - 1].cur_position) { + /* current top path is now empty, pop it, and try again */ + --stack->count; + free(stack->stack[stack->count].original_ptr); + } else { + /* get beginning of the path */ + char *cur_path = stack->stack[stack->count - 1].cur_position; + + /* find the end */ + char *dir_sep_pos = strchr(cur_path, '/'); + + *path = cur_path; + if (dir_sep_pos) { + if (dir_sep_pos == stack->stack[stack->count - 1].original_ptr) { + /* at the beginning of an absolute path, return root directory */ + *path = "/"; + } else { + /* terminate the path returned just after the end of the component */ + *dir_sep_pos = '\0'; + } + /* set the pointer for the next call */ + stack->stack[stack->count - 1].cur_position = dir_sep_pos + 1; + } else { + /* at the last component, set the pointer to the end of the string */ + stack->stack[stack->count - 1].cur_position += strlen(cur_path); + } + + /* return success */ + return 0; + } + } + + /* return stack was empty */ + return -1; +} + + +/* + * is_stack_empty + * Returns true if the stack is empty, false otherwise. + * parameters + * stack + * pointer to a dir_stack to get the next component + * returns + * 0 if stack is not empty + * 1 is stack is empty + */ +static int is_stack_empty(dir_stack *stack) +{ + /* since the empty items are not removed until the next call to + * get_next_component(), we need to check all the items on the stack + * and if any of them are not empty, return false, otherwise it truly + * is empty. + */ + int cur_head = stack->count - 1; + while (cur_head >= 0) { + if (*stack->stack[cur_head--].cur_position != '\0') { + return 0; + } + } + + return 1; +} + + +/* + * safe_is_path_trusted + * + * Returns the trustedness of the path. + * + * If the path is relative the path from the current working directory to + * the root must be trusted as defined in + * is_current_working_directory_trusted(). + * + * This checks directory entry by directory entry for trustedness, + * following the paths in symbolic links as discovered. Non-directory + * entries in a sticky bit directory are not trusted as untrusted users + * could have hard linked an old file at that name. + * + * SAFE_PATH_UNTRUSTED is returned if the path is not trusted somewhere. + * SAFE_PATH_TRUSTED_STICKY_DIR is returned if the path is trusted but ends + * in a stick bit directory. This path should only be used to + * make a true temporary file (opened using mkstemp(), and + * the pathname returned never used again except to remove the + * file in the same process), or to create a directory. + * SAFE_PATH_TRUSTED is returned only if the path given always refers to + * the same object and the object referred can not be modified. + * SAFE_PATH_TRUSTED_CONFIDENTIAL is returned if the path is + * SAFE_PATH_TRUSTED and the object referred to can not be read by + * untrusted users. This assumes the permissions on the object + * were always strong enough to return this during the life of the + * object. This confidentiality is only based on the the actual + * object, not the containing directories (for example a file with + * weak permissions in a confidential directory is not + * confidential). + * + * This function is not thread safe. This function changes the current + * working directory while checking the path, and restores it on exit. + * parameters + * pathname + * name of path to check + * safe_uids + * list of safe user ids + * safe_gids + * list of safe group ids + * returns + * -1 SAFE_PATH_ERROR + * 0 SAFE_PATH_UNTRUSTED + * 1 SAFE_PATH_TRUSTED_STICKY_DIR + * 2 SAFE_PATH_TRUSTED + * 3 SAFE_PATH_TRUSTED_CONFIDENTIAL + */ +int safe_is_path_trusted(const char *pathname, safe_id_range_list *trusted_uids, safe_id_range_list *trusted_gids) +{ + int r; + int status = SAFE_PATH_UNTRUSTED; + int num_tries; + int saved_dir; + dir_stack paths; + const char *path; + + if (!pathname || !trusted_uids || !trusted_gids) { + errno = EINVAL; + return SAFE_PATH_ERROR; + } + + init_dir_stack(&paths); + + saved_dir = open(".", O_RDONLY); + if (saved_dir == -1) { + goto restore_dir_and_exit; + } + + /* If the path is relative, check that the current working directory is a + * trusted file system object. If it is not then the path is not trusted + */ + if (*pathname != '/') { + /* relative path */ + status = is_current_working_directory_trusted(trusted_uids, trusted_gids); + if (status <= SAFE_PATH_UNTRUSTED) { + /* an error or untrusted current working directory */ + goto restore_dir_and_exit; + } + } + + /* start the stack with the pathname given */ + if (push_path_on_stack(&paths, pathname) < 0) { + status = SAFE_PATH_ERROR; + goto restore_dir_and_exit; + } + + while (!get_next_component(&paths, &path)) { + struct stat stat_buf; + mode_t m; + int prev_status; + + if (*path == '\0' || !strcmp(path, ".")) { + /* current directory, already checked */ + continue; + } + if (!strcmp(path, "/")) { + /* restarting at root, trust what is above root */ + status = SAFE_PATH_TRUSTED; + } + + prev_status = status; + + /* At this point if the directory component is '..', then the status + * should be set to be that of the grandparent directory, '../..', + * for the code below to work, which would require either recomputing + * the value, or keeping a cache of the value (which could then be used + * to get the trust level of '..' directly). + * + * This is not necessary at this point in the processing as we know that + * 1) '..' is a directory + * 2) '../..' trust was not SAFE_PATH_UNTRUSTED + * 3) the current trust level (status) is not SAFE_PATH_UNTRUSTED + * 4) the trust matrix rows are the same, when the parent is not + * SAFE_PATH_UNTRUSTED + * So not changing status will still result in the correct value + * + * WARNING: If any of these assumptions change, this will need to change. + */ + + num_tries = 0; + + try_lstat_again: + + if (++num_tries > SAFE_IS_PATH_TRUSTED_RETRY_MAX) { + /* let the user decide what to do */ + status = SAFE_PATH_ERROR; + errno = EAGAIN; + goto restore_dir_and_exit; + } + + /* check the next component in the path */ + r = lstat(path, &stat_buf); + if (r == -1) { + status = SAFE_PATH_ERROR; + goto restore_dir_and_exit; + } + + /* compute the new trust, from the parent trust and the current directory */ + status = is_component_in_dir_trusted(status, &stat_buf, trusted_uids, trusted_gids); + if (status <= SAFE_PATH_UNTRUSTED) { + goto restore_dir_and_exit; + } + + m = stat_buf.st_mode; + + if (S_ISLNK(m)) { + /* symbolic link found */ + size_t link_path_len = (size_t)stat_buf.st_size; + ssize_t readlink_len; + char *link_path = 0; + + link_path = (char *)malloc(link_path_len + 1); + if (link_path == 0) { + status = SAFE_PATH_ERROR; + errno = ENOMEM; + goto restore_dir_and_exit; + } + + /* Get the link's referent. readlink does not null terminate. + * Set buffer size to one more than the size from lstat to detect + * truncation. + */ + readlink_len = readlink(path, link_path, link_path_len + 1); + if (readlink_len == -1) { + free(link_path); + status = SAFE_PATH_ERROR; + goto restore_dir_and_exit; + } + + /* check for truncation of value */ + if ((size_t)readlink_len > link_path_len) { + free(link_path); + status = prev_status; + goto try_lstat_again; + } + + /* null terminate referent from readlink */ + link_path[readlink_len] = '\0'; + + /* add the path of the referent to the stack */ + if (push_path_on_stack(&paths, link_path) < 0) { + free(link_path); + status = SAFE_PATH_ERROR; + goto restore_dir_and_exit; + } + + /* restore value to that of containing directory */ + status = prev_status; + + free(link_path); + + continue; + } else if (!is_stack_empty(&paths)) { + /* More components remain, so change current working directory. + * path is not a symbolic link, if it is not a directory, chdir + * will fail and set errno to the proper value we want. + */ + r = chdir(path); + if (r == -1) { + status = SAFE_PATH_ERROR; + goto restore_dir_and_exit; + } + } + } + + + restore_dir_and_exit: + + /* free memory, restore cwd, and return value */ + destroy_dir_stack(&paths); + + if (saved_dir != -1) { + r = fchdir(saved_dir); + if (r == -1) { + status = SAFE_PATH_ERROR; + } + r = close(saved_dir); + if (r == -1) { + status = SAFE_PATH_ERROR; + } + } + + return status; +} + + + +/* + * safe_is_path_trusted_fork + * + * Returns the trustedness of the path. + * + * This function is thread/signal handler safe in that it does not change + * the current working directory. It forks a process to do the check, the + * new process changes itscurrent working directory as it does the checks + * by calling safe_is_path_trusted(). + * + * If the path is relative the path from the current working directory to + * the root must be trusted as defined in + * is_current_working_directory_trusted(). + * + * This checks directory entry by directory entry for trustedness, + * following the paths in symbolic links as discovered. Non-directory + * entries in a sticky bit directory are not trusted as untrusted users + * could have hard linked an old file at that name. + * + * SAFE_PATH_UNTRUSTED is returned if the path is not trusted somewhere. + * SAFE_PATH_TRUSTED_STICKY_DIR is returned if the path is trusted but ends + * in a stick bit directory. This path should only be used to + * make a true temporary file (opened using mkstemp(), and + * the pathname returned never used again except to remove the + * file in the same process), or to create a directory. + * SAFE_PATH_TRUSTED is returned only if the path given always refers to + * the same object and the object referred can not be modified. + * SAFE_PATH_TRUSTED_CONFIDENTIAL is returned if the path is + * SAFE_PATH_TRUSTED and the object referred to can not be read by + * untrusted users. This assumes the permissions on the object + * were always strong enough to return this during the life of the + * object. This confidentiality is only based on the actual + * object, not the containing directories (for example a file with + * weak permissions in a confidential directory is not + * confidential). + * + * parameters + * pathname + * name of path to check + * safe_uids + * list of safe user ids + * safe_gids + * list of safe group ids + * returns + * -1 SAFE_PATH_ERROR + * 0 SAFE_PATH_UNTRUSTED + * 1 SAFE_PATH_TRUSTED_STICKY_DIR + * 2 SAFE_PATH_TRUSTED + * 3 SAFE_PATH_TRUSTED_CONFIDENTIAL + */ +int safe_is_path_trusted_fork(const char *pathname, safe_id_range_list *trusted_uids, safe_id_range_list *trusted_gids) +{ + int r; + int status = 0; + pid_t pid; + int fd[2]; + + sigset_t no_sigchld_mask; + sigset_t save_mask; + sigset_t all_signals_mask; + + struct result_struct { + int status; + int err; + }; + + struct result_struct result; + + if (!pathname || !trusted_uids || !trusted_gids) { + errno = EINVAL; + return SAFE_PATH_ERROR; + } + + /* create a mask to block all signals */ + r = sigfillset(&all_signals_mask); + if (r < 0) { + return SAFE_PATH_ERROR; + } + + /* set no_sigchld_mask to current mask with SIGCHLD */ + r = sigprocmask(SIG_BLOCK, NULL, &no_sigchld_mask); + if (r < 0) { + return SAFE_PATH_ERROR; + } + r = sigaddset(&no_sigchld_mask, SIGCHLD); + if (r < 0) { + return SAFE_PATH_ERROR; + } + + /* block all signals to prevent a signal handler from running in our + * child */ + r = sigprocmask(SIG_SETMASK, &all_signals_mask, &save_mask); + if (r < 0) { + return SAFE_PATH_ERROR; + } + + /* create a pipe to communicate the results back */ + r = pipe(fd); + if (r < 0) { + goto restore_mask_and_exit; + } + + pid = fork(); + if (pid < 0) { + status = SAFE_PATH_ERROR; + goto restore_mask_and_exit; + } else if (pid == 0) { + /* in the child process + * + * SIGPIPE should be set to SIG_IGN if signal handling is ever + * unblocked in the child, so the child is not killed by SIGPIPE if the + * parent exits before the write. Since all signals are blocked in the + * child and only the child writes to the pipe, it is ok. + */ + + char *buf = (char *)&result; + ssize_t bytes_to_send = sizeof result; + + /* close the read end of the pipe */ + r = close(fd[0]); + + result.status = safe_is_path_trusted(pathname, trusted_uids, trusted_gids); + result.err = errno; + + /* send the result and errno back, handle EINTR and partial writes */ + while (bytes_to_send > 0) { + ssize_t bytes_sent = write(fd[1], buf, (size_t)bytes_to_send); + if (bytes_sent != bytes_to_send && errno != EINTR) { + status = SAFE_PATH_ERROR; + break; + } else if (bytes_sent > 0) { + buf += bytes_sent; + bytes_to_send -= bytes_sent; + } + } + + r = close(fd[1]); + if (r < 0) { + status = SAFE_PATH_ERROR; + } + + /* do not do any cleanup (atexit, etc) leave it to the parent */ + _exit(status); + } else { + /* in the parent process */ + + char *buf = (char *)&result; + ssize_t bytes_to_read = sizeof result; + int child_status; + + /* allow all signals except SIGCHLD from being sent, + * so the application does not see our child die */ + r = sigprocmask(SIG_SETMASK, &no_sigchld_mask, NULL); + if (r < 0) { + status = SAFE_PATH_ERROR; + } + + /* close the write end of the pipe */ + r = close(fd[1]); + if (r < 0) { + status = SAFE_PATH_ERROR; + } + + result.err = 0; + + /* read the result and errno, handle EINTR and partial reads */ + while (status != SAFE_PATH_ERROR && bytes_to_read > 0) { + ssize_t bytes_read = read(fd[0], buf, (size_t)bytes_to_read); + if (bytes_read != bytes_to_read && errno != EINTR) { + status = SAFE_PATH_ERROR; + } else if (bytes_read > 0) { + buf += bytes_read; + bytes_to_read -= bytes_read; + } else if (bytes_read == 0) { + /* EOF - pipe was closed before all the data was written */ + status = SAFE_PATH_ERROR; + } + } + + if (status == 0) { + /* successfully got result and errno from child set them */ + status = result.status; + errno = result.err; + } + + r = close(fd[0]); + if (r < 0) { + status = SAFE_PATH_ERROR; + } + + while (waitpid(pid, &child_status, 0) < 0) { + if (errno != EINTR) { + status = SAFE_PATH_ERROR; + goto restore_mask_and_exit; + } + } + + if (!WIFEXITED(child_status) && WEXITSTATUS(child_status) != 0) { + status = SAFE_PATH_ERROR; + } + } + + + restore_mask_and_exit: + + r = sigprocmask(SIG_SETMASK, &save_mask, NULL); + if (r < 0) { + status = r; + } + + return status; +} + + + +/* + * append_dir_entry_to_path + * + * Creates a new path that starts in "path" and moves to "name". Path and + * name are both assumed to contain no symbolic links. + * + * If name is "/", path is set to "/". If name is "" or ".", path is + * unchanged. If name is "..", the last component of path is removed if + * it exists and is not "", ".", or "..". Otherwise, "/name" is appended + * to the path. If path exceed the path buffer, ENAMETOOLONG is returned + * and path is left unchanged. + * + * parameters + * path + * a pointer to the beginning of the path buffer, that is the + * current directory to which name is relative. Path contains no + * symbolic links. + * path_end + * a pointer to a pointer to the current end of the path. Updated + * to reflect the new end of path on success. + * buf_end + * a pointer to one past the end of the path buffer + * name + * the new path component to traverse relative to path. It is + * assumed to be a single directory name (no directory separators, + * "/", in name), or the root directory "/"; and the name is not + * a symbolic link. + * returns + * 0 on success + * -1 on error + */ +static int append_dir_entry_to_path(char *path, char **path_end, char *buf_end, const char *name) +{ + char *old_path_end = *path_end; + + if (*name == '\0' || !strcmp(name, ".")) { + /* current working directory name, skip */ + return 0; + } + + if (!strcmp(name, "/")) { + /* reset the path, if name is the root directory */ + *path_end = path; + } + + if (!strcmp(name, "..")) { + /* if path is empty, skip and append ".." later */ + if (path != *path_end) { + /* find the beginning of the last component */ + char *last_comp = *path_end; + while (last_comp > path && last_comp[-1] != '/') { + --last_comp; + } + + if (strcmp(last_comp, "") && strcmp(last_comp, ".") && strcmp(last_comp, "..")) { + /* if not current or parent directory, remove component */ + *path_end = last_comp; + if (last_comp > path) { + --*path_end; + } + **path_end = '\0'; + } + + return 0; + } + } + + if (*path_end != path && (*path_end)[-1] != '/') { + if (*path_end + 1 >= buf_end) { + errno = ENAMETOOLONG; + return -1; + } + + *(*path_end)++ = '/'; + *(*path_end) = '\0'; + } + + /* copy component name to the end, except null */ + while (*path_end < buf_end && *name) { + *(*path_end)++ = *name++; + } + + if (*name) { + /* not enough room for path, return error */ + errno = ENAMETOOLONG; + *old_path_end = '\0'; + return -1; + } + + /* null terminate the path */ + **path_end = '\0'; + + return 0; +} + + + +/* + * safe_is_path_trusted_r + * + * Returns the trustedness of the path. + * + * If the path is relative the path from the current working directory to + * the root must be trusted as defined in + * is_current_working_directory_trusted(). + * + * This checks directory entry by directory entry for trustedness, + * following the paths in symbolic links as discovered. Non-directory + * entries in a sticky bit directory are not trusted as untrusted users + * could have hard linked an old file at that name. + * + * SAFE_PATH_UNTRUSTED is returned if the path is not trusted somewhere. + * SAFE_PATH_TRUSTED_STICKY_DIR is returned if the path is trusted but ends + * in a stick bit directory. This path should only be used to + * make a true temporary file (opened using mkstemp(), and + * the pathname returned never used again except to remove the + * file in the same process), or to create a directory. + * SAFE_PATH_TRUSTED is returned only if the path given always refers to + * the same object and the object referred can not be modified. + * SAFE_PATH_TRUSTED_CONFIDENTIAL is returned if the path is + * SAFE_PATH_TRUSTED and the object referred to can not be read by + * untrusted users. This assumes the permissions on the object + * were always strong enough to return this during the life of the + * object. This confidentiality is only based on the actual + * object, not the containing directories (for example a file with + * weak permissions in a confidential directory is not + * confidential). + * parameters + * pathname + * name of path to check + * safe_uids + * list of safe user ids + * safe_gids + * list of safe group ids + * returns + * -1 SAFE_PATH_ERROR + * 0 SAFE_PATH_UNTRUSTED + * 1 SAFE_PATH_TRUSTED_STICKY_DIR + * 2 SAFE_PATH_TRUSTED + * 3 SAFE_PATH_TRUSTED_CONFIDENTIAL + */ +int safe_is_path_trusted_r(const char *pathname, safe_id_range_list *trusted_uids, safe_id_range_list *trusted_gids) +{ + int r; + int status = SAFE_PATH_UNTRUSTED; + int num_tries; + dir_stack paths; + const char *comp_name; + char path[PATH_MAX]; + char *path_end = path; + char *prev_path_end; + + if (!pathname || !trusted_uids || !trusted_gids) { + errno = EINVAL; + return SAFE_PATH_ERROR; + } + + init_dir_stack(&paths); + + if (*pathname != '/') { + /* relative path */ + status = is_current_working_directory_trusted_r(trusted_uids, trusted_gids); + if (status <= SAFE_PATH_UNTRUSTED) { + /* an error or untrusted current working directory */ + goto cleanup_and_exit; + } + } + + /* start the stack with the pathname given */ + if (push_path_on_stack(&paths, pathname) < 0) { + status = SAFE_PATH_ERROR; + goto cleanup_and_exit; + } + + while (!get_next_component(&paths, &comp_name)) { + struct stat stat_buf; + mode_t m; + int prev_status; + + if (*comp_name == '\0' || !strcmp(comp_name, ".")) { + /* current directory, already checked */ + continue; + } + if (!strcmp(comp_name, "/")) { + /* restarting at root, trust what is above root */ + status = SAFE_PATH_TRUSTED; + } + + prev_path_end = path_end; + prev_status = status; + + r = append_dir_entry_to_path(path, &path_end, path + sizeof(path), comp_name); + if (r == -1) { + status = SAFE_PATH_ERROR; + goto cleanup_and_exit; + } + + /* At this point if the directory component is '..', then the status + * should be set to be that of the grandparent directory, '../..', + * for the code below to work, which would require either recomputing + * the value, or keeping a cache of the value (which could then be used + * to get the trust level of '..' directly). + * + * This is not necessary at this point in the processing as we know that + * 1) '..' is a directory + * 2) '../..' trust was not SAFE_PATH_UNTRUSTED + * 3) the current trust level (status) is not SAFE_PATH_UNTRUSTED + * 4) the trust matrix rows are the same, when the parent is not + * SAFE_PATH_UNTRUSTED + * So not changing status will still result in the correct value + * + * WARNING: If any of these assumptions change, this will need to change. + */ + + num_tries = 0; + + try_lstat_again: + + if (++num_tries > SAFE_IS_PATH_TRUSTED_RETRY_MAX) { + /* let the user decide what to do */ + status = SAFE_PATH_ERROR; + errno = EAGAIN; + goto cleanup_and_exit; + } + + /* check the next component in the path */ + r = lstat(path, &stat_buf); + if (r == -1) { + status = SAFE_PATH_ERROR; + goto cleanup_and_exit; + } + + /* compute the new trust, from the parent trust and the current directory */ + status = is_component_in_dir_trusted(status, &stat_buf, trusted_uids, trusted_gids); + if (status <= SAFE_PATH_UNTRUSTED) { + goto cleanup_and_exit; + } + + m = stat_buf.st_mode; + + if (S_ISLNK(m)) { + /* symbolic link found */ + size_t link_path_len = (size_t)stat_buf.st_size; + ssize_t readlink_len; + char *link_path = (char *)malloc(link_path_len + 1); + + if (link_path == 0) { + status = SAFE_PATH_ERROR; + errno = ENOMEM; + goto cleanup_and_exit; + } + + /* Get the link's referent. readlink does not null terminate. + * Set buffer size to one more than the size from lstat to detect + * truncation. + */ + readlink_len = readlink(path, link_path, link_path_len + 1); + if (readlink_len == -1) { + free(link_path); + status = SAFE_PATH_ERROR; + goto cleanup_and_exit; + } + + if ((size_t)readlink_len > link_path_len) { + free(link_path); + status = prev_status; + goto try_lstat_again; + } + + /* null terminate referent from readlink */ + link_path[readlink_len] = '\0'; + + /* add path to the stack */ + if (push_path_on_stack(&paths, link_path) < 0) { + free(link_path); + status = SAFE_PATH_ERROR; + goto cleanup_and_exit; + } + + free(link_path); + + /* restore values to the containing directory */ + status = prev_status; + path_end = prev_path_end; + *path_end = '\0'; + } else { + if (!is_stack_empty(&paths) && !S_ISDIR(stat_buf.st_mode)) { + status = SAFE_PATH_ERROR; + errno = ENOTDIR; + goto cleanup_and_exit; + } + } + } + + + cleanup_and_exit: + + /* free memory, restore cwd, and return value */ + destroy_dir_stack(&paths); + + /* if this algorithm failed because the pathname was too long, + * try the fork version on the same pathname as it can handle all valid paths + */ + if (status == SAFE_PATH_ERROR && errno == ENAMETOOLONG) { + status = safe_is_path_trusted_fork(pathname, trusted_uids, trusted_gids); + } + + + return status; +} diff --git a/src/utils/file_io/safefile/safe_is_path_trusted.h b/src/utils/file_io/safefile/safe_is_path_trusted.h new file mode 100644 index 00000000..834e3f05 --- /dev/null +++ b/src/utils/file_io/safefile/safe_is_path_trusted.h @@ -0,0 +1,67 @@ +#ifndef SAFE_IS_PATH_TRUSTED_H_ +#define SAFE_IS_PATH_TRUSTED_H_ + +/* + * safefile package http://www.cs.wisc.edu/~kupsch/safefile + * + * Copyright 2007-2008, 2011 James A. Kupsch + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +struct safe_id_range_list; + +#ifndef SAFE_IS_PATH_TRUSTED_RETRY_MAX +#define SAFE_IS_PATH_TRUSTED_RETRY_MAX 50 +#endif + + + +enum { + SAFE_PATH_ERROR = -1, + SAFE_PATH_UNTRUSTED, + SAFE_PATH_TRUSTED_STICKY_DIR, + SAFE_PATH_TRUSTED, + SAFE_PATH_TRUSTED_CONFIDENTIAL + }; + +int safe_is_path_trusted( + const char *pathname, + struct safe_id_range_list *trusted_uids, + struct safe_id_range_list *trusted_gids + ); +int safe_is_path_trusted_fork( + const char *pathname, + struct safe_id_range_list *trusted_uids, + struct safe_id_range_list *trusted_gids + ); +int safe_is_path_trusted_r( + const char *pathname, + struct safe_id_range_list *trusted_uids, + struct safe_id_range_list *trusted_gids + ); + + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/src/utils/json.c b/src/utils/json.c index 0dcf7479..562503ab 100644 --- a/src/utils/json.c +++ b/src/utils/json.c @@ -111,6 +111,21 @@ int isJSONObject(const char* json) { return res; } +int isJSONArray(const char* json) { + if (NULL == json) { + oidc_setArgNullFuncError(__func__); + return 0; + } + initCJSON(); + cJSON* cj = stringToJsonDontLogError(json); + if (cj == NULL) { + return 0; + } + int res = cJSON_IsArray(cj); + cJSON_Delete(cj); + return res; +} + /** * @brief safly calls cJSON_Delete freeing the cJSON Object * @param cjson the cJSON Object to be freed @@ -184,7 +199,18 @@ char* getJSONItemValue(cJSON* valueItem) { char* value = cJSON_GetStringValue(valueItem); return strValid(value) ? oidc_strcopy(value) : NULL; } - return cJSON_PrintUnformatted(valueItem); + if (cJSON_IsTrue(valueItem)) { + return oidc_strcopy("1"); + } + if (cJSON_IsFalse(valueItem)) { + return oidc_strcopy("0"); + } + char* v = cJSON_PrintUnformatted(valueItem); + if (strcaseequal(v, "null")) { + secFree(v); + return NULL; + } + return v; } /** @@ -457,6 +483,16 @@ cJSON* jsonAddJSON(cJSON* cjson, const char* key, cJSON* item) { cJSON_AddItemToObject(cjson, key, item); return cjson; } +cJSON* jsonAddBoolValue(cJSON* cjson, const char* key, + const unsigned char value) { + if (NULL == cjson || NULL == key) { + oidc_setArgNullFuncError(__func__); + return NULL; + } + initCJSON(); + cJSON_AddBoolToObject(cjson, key, value); + return cjson; +} cJSON* jsonArrayAddStringValue(cJSON* cjson, const char* value) { if (value == NULL) { @@ -470,13 +506,23 @@ cJSON* jsonArrayAddStringValue(cJSON* cjson, const char* value) { return cjson; } +/** + * @brief converts a list of strings into a cJSON JSONArray + * @param list a pointer to the list to be converted + * @return a pointer to a cJSON JSONArray. Has to be freed after usage using + * @c secFreeJson + */ +cJSON* stringListToJSONArray(list_t* list) { + return listToJSONArray(list, (cJSON * (*)(void*)) cJSON_CreateString); +} + /** * @brief converts a list into a cJSON JSONArray * @param list a pointer to the list to be converted * @return a pointer to a cJSON JSONArray. Has to be freed after usage using * @c secFreeJson */ -cJSON* listToJSONArray(list_t* list) { +cJSON* listToJSONArray(list_t* list, cJSON* (*toJSON)(void*)) { if (list == NULL) { oidc_setArgNullFuncError(__func__); return NULL; @@ -490,7 +536,7 @@ cJSON* listToJSONArray(list_t* list) { list_node_t* node; list_iterator_t* it = list_iterator_new(list, LIST_HEAD); while ((node = list_iterator_next(it))) { - cJSON_AddItemToArray(json, cJSON_CreateString(node->val)); + cJSON_AddItemToArray(json, toJSON(node->val)); } list_iterator_destroy(it); return json; diff --git a/src/utils/json.h b/src/utils/json.h index 1ce45325..a796554d 100644 --- a/src/utils/json.h +++ b/src/utils/json.h @@ -18,6 +18,7 @@ oidc_error_t getJSONValuesFromString(const char* json, struct key_value* pairs, int jsonHasKey(const cJSON* cjson, const char* key); int jsonStringHasKey(const char* json, const char* key); int isJSONObject(const char* json); +int isJSONArray(const char* json); int jsonArrayIsEmpty(cJSON* json); char* jsonToString(cJSON* cjson); @@ -28,7 +29,8 @@ list_t* JSONArrayToList(const cJSON* cjson); list_t* JSONArrayStringToList(const char* json); char* JSONArrayToDelimitedString(const cJSON* cjson, char* delim); char* JSONArrayStringToDelimitedString(const char* json, char* delim); -cJSON* listToJSONArray(list_t* list); +cJSON* stringListToJSONArray(list_t* list); +cJSON* listToJSONArray(list_t* list, cJSON* (*toJSON)(void*)); cJSON* generateJSONObject(const char* k1, int type1, const char* v1, ...); oidc_error_t setJSONValue(cJSON* cjson, const char* key, const char* value); @@ -40,6 +42,8 @@ cJSON* jsonAddObjectValue(cJSON* cjson, const char* key, cJSON* jsonAddArrayValue(cJSON* cjson, const char* key, const char* json_array); cJSON* jsonAddNumberValue(cJSON* cjson, const char* key, const double value); cJSON* jsonAddStringValue(cJSON* cjson, const char* key, const char* value); +cJSON* jsonAddBoolValue(cJSON* cjson, const char* key, + const unsigned char value); cJSON* jsonArrayAddStringValue(cJSON* cjson, const char* value); cJSON* generateJSONArray(char* v1, ...); cJSON* mergeJSONObjects(const cJSON* j1, const cJSON* j2); diff --git a/src/utils/key_value.h b/src/utils/key_value.h index 0a249e6b..fce3edf9 100644 --- a/src/utils/key_value.h +++ b/src/utils/key_value.h @@ -25,6 +25,9 @@ static inline void secFreeKeyValuePairs(struct key_value* pairs, size_t size) { #define KEY_VALUE_VARS(...) \ CALL_MACRO_X_FOR_EACH_WITH_N(KEY_VALUE_VAR, __VA_ARGS__) +#define SEC_FREE_KEY_VALUES() \ + secFreeKeyValuePairs(pairs, sizeof(pairs) / sizeof(*pairs)) + #define INIT_KEY_VALUE(...) \ struct key_value pairs[COUNT_VARARGS(__VA_ARGS__)]; \ CALL_MACRO_X_FOR_EACH_WITH_N(KEY_VALUE, __VA_ARGS__) @@ -32,18 +35,24 @@ static inline void secFreeKeyValuePairs(struct key_value* pairs, size_t size) { #define GET_JSON_VALUES_RETURN_X_ONERROR(json, returnvalue) \ if (getJSONValuesFromString((json), pairs, sizeof(pairs) / sizeof(*pairs)) < \ 0) { \ - secFreeKeyValuePairs(pairs, sizeof(pairs) / sizeof(*pairs)); \ + SEC_FREE_KEY_VALUES(); \ return (returnvalue); \ } +#define GET_JSON_VALUES_CJSON_RETURN_X_ONERROR(json, returnvalue) \ + if (getJSONValues((json), pairs, sizeof(pairs) / sizeof(*pairs)) < 0) { \ + SEC_FREE_KEY_VALUES(); \ + return (returnvalue); \ + } + #define GET_JSON_VALUES_RETURN_NULL_ONERROR(json) \ GET_JSON_VALUES_RETURN_X_ONERROR((json), NULL) #define GET_JSON_VALUES_RETURN_OIDCERRNO_ONERROR(json) \ GET_JSON_VALUES_RETURN_X_ONERROR((json), oidc_errno) -#define SEC_FREE_KEY_VALUES() \ - secFreeKeyValuePairs(pairs, sizeof(pairs) / sizeof(*pairs)) +#define GET_JSON_VALUES_CJSON_RETURN_NULL_ONERROR(json) \ + GET_JSON_VALUES_CJSON_RETURN_X_ONERROR((json), NULL) #define CALL_GETJSONVALUES(json) \ getJSONValuesFromString((json), pairs, sizeof(pairs) / sizeof(*pairs)) diff --git a/src/utils/listUtils.c b/src/utils/listUtils.c index b9dac008..24029ce0 100644 --- a/src/utils/listUtils.c +++ b/src/utils/listUtils.c @@ -9,7 +9,12 @@ #include "utils/printer.h" #include "utils/string/stringUtils.h" -char* delimitedStringToJSONArray(char* str, char delimiter) { +char* delimitedStringToJSONArray(const char* str, char delimiter) { + return delimitedStringToJSONArrayFmt(str, delimiter, "\"%s\""); +} + +char* delimitedStringToJSONArrayFmt(const char* str, char delimiter, + const char* valueFmt) { if (str == NULL) { oidc_setArgNullFuncError(__func__); return NULL; @@ -18,18 +23,25 @@ char* delimitedStringToJSONArray(char* str, char delimiter) { size_t size = strCountChar(str, delimiter) + 1; char* copy = oidc_sprintf("%s", str); char* delim = oidc_sprintf("%c", delimiter); - char* json = oidc_sprintf("\"%s\"", strtok(copy, delim)); + char* json = oidc_sprintf(valueFmt, strtok(copy, delim)); size_t i; + char* fmt = oidc_strcat("%s, ", valueFmt); for (i = 1; i < size; i++) { - char* tmp = oidc_sprintf("%s, \"%s\"", json, strtok(NULL, delim)); + char* tok = strtok(NULL, delim); + if (!strValid(tok)) { + continue; + } + char* tmp = oidc_sprintf(fmt, json, tok); secFree(json); if (tmp == NULL) { secFree(delim); secFree(copy); + secFree(fmt); return NULL; } json = tmp; } + secFree(fmt); secFree(delim); secFree(copy); char* tmp = oidc_sprintf("[%s]", json); @@ -46,7 +58,7 @@ char* listToJSONArrayString(list_t* list) { return NULL; } - cJSON* json = listToJSONArray(list); + cJSON* json = stringListToJSONArray(list); if (json == NULL) { return NULL; } @@ -55,20 +67,28 @@ char* listToJSONArrayString(list_t* list) { return str; } +list_t* newListWithSingleValue(const char* str) { + list_t* list = list_new(); + list->free = (void (*)(void*)) & _secFree; + list->match = (matchFunction)strequal; + list_rpush(list, list_node_new(oidc_strcopy(str))); + return list; +} + list_t* delimitedStringToList(const char* str, char delimiter) { if (str == NULL) { oidc_setArgNullFuncError(__func__); return NULL; } - char* copy = oidc_sprintf("%s", str); + char* copy = oidc_strcopy(str); char* delim = oidc_sprintf("%c", delimiter); list_t* list = list_new(); list->free = (void (*)(void*)) & _secFree; list->match = (matchFunction)strequal; char* elem = strtok(copy, delim); while (elem != NULL) { - list_rpush(list, list_node_new(oidc_sprintf(elem))); + list_rpush(list, list_node_new(oidc_strcopy(elem))); elem = strtok(NULL, delim); } secFree(delim); @@ -98,6 +118,22 @@ char* listToDelimitedString(list_t* list, char* delimiter) { return str; } +const char** listToArray(list_t* list) { + if (list == NULL) { + return NULL; + } + if (list->len == 0) { + return NULL; + } + const char** arr = + secAlloc(sizeof(const char*) * + (list->len + 1)); // the +1 will add a NULL pointer to the end + for (unsigned int i = 0; i < list->len; i++) { + arr[i] = (const char*)list_at(list, (int)i)->val; + } + return arr; +} + void* passThrough(void* ptr) { return ptr; } list_t* createList(int copyValues, char* s, ...) { @@ -138,12 +174,12 @@ list_t* intersectLists(list_t* a, list_t* b) { return l; } -list_t* copyList(list_t* a) { +list_t* copyList(const list_t* a) { list_t* l = list_new(); l->free = _secFree; l->match = a->match; list_node_t* node; - list_iterator_t* it = list_iterator_new(a, LIST_HEAD); + list_iterator_t* it = list_iterator_new((list_t*)a, LIST_HEAD); while ((node = list_iterator_next(it))) { list_rpush(l, list_node_new(oidc_strcopy(node->val))); } @@ -260,11 +296,11 @@ void list_removeIfFound(list_t* l, const void* v) { // Merges two subarrays of arr[]. // First subarray is arr[l..m] // Second subarray is arr[m+1..r] -void _merge(void* arr[], int l, int m, int r, - int (*comp)(const void*, const void*)) { - int i, j, k; - int n1 = m - l + 1; - int n2 = r - m; +void _merge(void* arr[], unsigned int l, unsigned int m, unsigned int r, + matchFunction comp) { + unsigned int i, j, k; + unsigned int n1 = m - l + 1; + unsigned int n2 = r - m; /* create temp arrays */ void* L[n1]; @@ -308,12 +344,12 @@ void _merge(void* arr[], int l, int m, int r, /* l is for left index and r is right index of the sub-array of arr to be sorted */ -void mergeSort(void* arr[], int l, int r, - int (*comp)(const void*, const void*)) { +void mergeSort(void* arr[], unsigned int l, unsigned int r, + matchFunction comp) { if (l < r) { // Same as (l+r)/2, but avoids overflow for // large l and h - int m = l + (r - l) / 2; + unsigned int m = l + (r - l) / 2; // Sort first and second halves mergeSort(arr, l, m, comp); @@ -323,14 +359,14 @@ void mergeSort(void* arr[], int l, int r, } } -void list_mergeSort(list_t* l, int (*comp)(const void*, const void*)) { +void list_mergeSort(list_t* l, matchFunction comp) { void* arr[l->len]; - for (size_t i = 0; i < l->len; i++) { arr[i] = list_at(l, i)->val; } + for (size_t i = 0; i < l->len; i++) { arr[i] = list_ats(l, i)->val; } mergeSort(arr, 0, l->len - 1, comp); - for (size_t i = 0; i < l->len; i++) { list_at(l, i)->val = arr[i]; } + for (size_t i = 0; i < l->len; i++) { list_ats(l, i)->val = arr[i]; } } -void secFreeList(list_t* l) { +void _secFreeList(list_t* l) { if (l == NULL) { return; } diff --git a/src/utils/listUtils.h b/src/utils/listUtils.h index b2de5416..91097495 100644 --- a/src/utils/listUtils.h +++ b/src/utils/listUtils.h @@ -9,23 +9,35 @@ typedef int (*matchFunction)(void*, void*); typedef void (*freeFunction)(void*); -char* delimitedStringToJSONArray(char* str, char delimiter); +char* delimitedStringToJSONArray(const char* str, char delimiter); +char* delimitedStringToJSONArrayFmt(const char* str, char delimiter, + const char* valueFmt); list_t* delimitedStringToList(const char* str, char delimiter); char* listToDelimitedString(list_t* list, char* delimiter); -list_t* copyList(list_t* a); +const char** listToArray(list_t* list); +list_t* copyList(const list_t* a); list_t* mergeLists(list_t* a, list_t* b); list_t* intersectLists(list_t* a, list_t* b); list_t* subtractLists(list_t* a, list_t* b); -char* subtractListStrings(const char* a, const char* b, const char del); +char* subtractListStrings(const char* a, const char* b, char del); char* listToJSONArrayString(list_t* list); list_node_t* findInList(list_t* l, const void* v); list_t* findAllInList(list_t* l, const void* v); void list_removeIfFound(list_t* l, const void* v); -void list_mergeSort(list_t* l, int (*comp)(const void*, const void*)); -void secFreeList(list_t* l); +void list_mergeSort(list_t* l, matchFunction comp); +void _secFreeList(list_t* l); list_t* createList(int copyValues, char* s, ...); list_t* list_addStringIfNotFound(list_t* l, char* v); int listValid(const list_t* l); list_node_t* list_ats(list_t* l, size_t pos); +list_t* newListWithSingleValue(const char* str); + +#ifndef secFreeList +#define secFreeList(ptr) \ + do { \ + _secFreeList((ptr)); \ + (ptr) = NULL; \ + } while (0) +#endif // secFreeList #endif // LIST_UTILS_H diff --git a/src/utils/logger.c b/src/utils/logger.c index a447d588..765cd900 100644 --- a/src/utils/logger.c +++ b/src/utils/logger.c @@ -63,13 +63,13 @@ void logger_open(const char* _logger_name) { logger_name = _logger_name; } -void logger(int log_level, const char* msg, ...) { +void _logger(int log_level, const char* msg, ...) { va_list args; va_start(args, msg); vsyslog(LOG_AUTHPRIV | log_level, msg, args); } -void loggerTerminal(int log_level, const char* msg, ...) { +void _loggerTerminal(int log_level, const char* msg, ...) { va_list args, copy; va_start(args, msg); va_copy(copy, args); @@ -107,23 +107,23 @@ void logger_open(const char* _logger_name) { logger_name = oidc_strcopy(_logger_name); } -void _logger(int terminal, int _log_level, const char* msg, va_list args) { +void __logger(int terminal, int _log_level, const char* msg, va_list args) { if (_log_level >= log_level) { own_log(terminal, _log_level, msg, args); } } -void loggerTerminal(int _log_level, const char* msg, ...) { +void _loggerTerminal(int _log_level, const char* msg, ...) { va_list args; va_start(args, msg); - _logger(1, _log_level, msg, args); + __logger(1, _log_level, msg, args); va_end(args); } -void logger(int _log_level, const char* msg, ...) { +void _logger(int _log_level, const char* msg, ...) { va_list args; va_start(args, msg); - _logger(0, _log_level, msg, args); + __logger(0, _log_level, msg, args); va_end(args); } diff --git a/src/utils/logger.h b/src/utils/logger.h index beee3d4d..ca0b18ad 100644 --- a/src/utils/logger.h +++ b/src/utils/logger.h @@ -24,10 +24,19 @@ #endif +#define _S1(x) #x +#define _S2(x) _S1(x) +#define LOCATION "(" __FILE__ ":" _S2(__LINE__) ") " +#define LOG_FMT(x) LOCATION x + void logger_open(const char* logger_name); -void logger(int log_level, const char* msg, ...); -void loggerTerminal(int log_level, const char* msg, ...); +void _logger(int log_level, const char* msg, ...); +void _loggerTerminal(int log_level, const char* msg, ...); int logger_setlogmask(int); int logger_setloglevel(int); +#define logger(LOG_LEVEL, MSG, ...) \ + _logger(LOG_LEVEL, LOG_FMT(MSG), ##__VA_ARGS__) +#define loggerTerminal(LOG_LEVEL, MSG, ...) \ + _loggerTerminal(LOG_LEVEL, LOG_FMT(MSG), ##__VA_ARGS__) #endif // OIDC_LOGGER_H diff --git a/src/utils/matcher.c b/src/utils/matcher.c index c72fb0f7..965b8b64 100644 --- a/src/utils/matcher.c +++ b/src/utils/matcher.c @@ -29,4 +29,4 @@ int matchUrls(const char* a, const char* b) { return 0; } return compIssuerUrls(a, b); -} +} \ No newline at end of file diff --git a/src/utils/oidc/device.c b/src/utils/oidc/device.c index a48afc3f..01f28b00 100644 --- a/src/utils/oidc/device.c +++ b/src/utils/oidc/device.c @@ -2,13 +2,13 @@ #include #include -#include #include "defines/ipc_values.h" #include "defines/oidc_values.h" #include "ipc/cryptCommunicator.h" #include "ipc/pipe.h" #include "oidc-agent/oidc/device_code.h" +#include "utils/config/issuerConfig.h" #include "utils/json.h" #include "utils/printer.h" #include "utils/string/stringUtils.h" @@ -22,11 +22,13 @@ char* _pollDeviceCode(const char* json_device, size_t interval, json_device, only_at) : ipc_cryptCommunicate(remote, REQUEST_DEVICE, json_device, only_at); + parse_response: if (NULL == res) { return NULL; } INIT_KEY_VALUE(IPC_KEY_STATUS, OIDC_KEY_ERROR, IPC_KEY_CONFIG, - OIDC_KEY_ACCESSTOKEN); + OIDC_KEY_ACCESSTOKEN, IPC_KEY_REQUEST, INT_IPC_KEY_ACTION, + IPC_KEY_ISSUERURL, IPC_KEY_SHORTNAME); if (CALL_GETJSONVALUES(res) < 0) { printError("Could not decode json: %s\n", res); printError("This seems to be a bug. Please hand in a bug report.\n"); @@ -35,7 +37,8 @@ char* _pollDeviceCode(const char* json_device, size_t interval, return NULL; } secFree(res); - KEY_VALUE_VARS(status, error, config, at); + KEY_VALUE_VARS(status, error, config, at, request, action, issuer, + shortname); if (_error) { if (strequal(_error, OIDC_SLOW_DOWN)) { interval++; @@ -51,6 +54,12 @@ char* _pollDeviceCode(const char* json_device, size_t interval, SEC_FREE_KEY_VALUES(); return NULL; } + if (pipes && strcaseequal(_request, INT_REQUEST_VALUE_UPD_ISSUER)) { + oidcp_updateIssuerConfig(_action, _issuer, _shortname); + SEC_FREE_KEY_VALUES(); + res = ipc_communicateThroughPipe(*pipes, RESPONSE_SUCCESS); + goto parse_response; + } secFree(_status); if (only_at) { secFree(_config); diff --git a/src/utils/oidc_error.c b/src/utils/oidc_error.c index bbeea761..c6a253f0 100644 --- a/src/utils/oidc_error.c +++ b/src/utils/oidc_error.c @@ -45,6 +45,7 @@ char* oidc_serrorFor(oidc_error_t err) { case OIDC_EALLOC: return "memory alloc failed"; case OIDC_EMEM: return "system out of memory"; case OIDC_EEOF: return "empty file"; + case OIDC_EPERM: return "socket path location is not trustworthy"; case OIDC_EFOPEN: return "could not open file"; case OIDC_EFREAD: return "could not read file"; case OIDC_EWRITE: return "could not write"; diff --git a/src/utils/oidc_error.h b/src/utils/oidc_error.h index cfdc85fd..9483f334 100644 --- a/src/utils/oidc_error.h +++ b/src/utils/oidc_error.h @@ -7,6 +7,7 @@ enum _oidc_error { OIDC_EALLOC = -2, OIDC_EMEM = -3, + OIDC_EPERM = -4, OIDC_EEOF = -5, OIDC_EFOPEN = -6, OIDC_EFREAD = -7, diff --git a/src/utils/password_entry.h b/src/utils/password_entry.h index a5e4b040..4204f962 100644 --- a/src/utils/password_entry.h +++ b/src/utils/password_entry.h @@ -19,7 +19,7 @@ struct password_entry { }; #define PW_TYPE_MEM 0x01 -#define PW_TYPE_MNG 0x02 +// #define PW_TYPE_MNG 0x02 //dropped in v5.0.0 #define PW_TYPE_CMD 0x04 #define PW_TYPE_PRMT 0x08 #define PW_TYPE_FILE 0x10 diff --git a/src/utils/prompting/getprompt.c b/src/utils/prompting/getprompt.c new file mode 100644 index 00000000..5085632e --- /dev/null +++ b/src/utils/prompting/getprompt.c @@ -0,0 +1,35 @@ +#include "getprompt.h" + +#include "utils/json.h" +#include "utils/oidc_error.h" +#include "utils/prompting/templates/templates.h" +#include "utils/string/stringUtils.h" +#include "wrapper/mustache.h" + +char* getprompt(const char* template, cJSON* data) { + char* result = NULL; + size_t resultLen = 0; + int ret = mustach_cJSON_mem(template, 0, data, 0, &result, &resultLen); + if (ret != 0) { + oidc_setErrnoError(); + return NULL; + } + char* str = oidc_strcopy(result); + free(result); + return str; +} + +char* get_general_prompt(const char* label) { + if (prompt_mode() == PROMPT_MODE_GUI) { + return oidc_sprintf("

Enter %s

Please enter %s:", label, label); + } + return oidc_sprintf("Please enter %s", label); +} + +char* get_general_select_prompt(const char* label) { + if (prompt_mode() == PROMPT_MODE_GUI) { + return oidc_sprintf("

Select %s

Please select %s:", label, + label); + } + return oidc_sprintf("Please select %s", label); +} diff --git a/src/utils/prompting/getprompt.h b/src/utils/prompting/getprompt.h new file mode 100644 index 00000000..e81e8173 --- /dev/null +++ b/src/utils/prompting/getprompt.h @@ -0,0 +1,16 @@ +#ifndef OIDC_GETTEXT_H +#define OIDC_GETTEXT_H + +#include "prompt_mode.h" +#include "templates/templates.h" +#include "wrapper/cjson.h" + +#define PROMPTTEMPLATE(MSGID) \ + prompt_mode() == PROMPT_MODE_GUI ? PROMPTTEMPLATE_##MSGID##_GUI \ + : PROMPTTEMPLATE_##MSGID##_CLI + +char* getprompt(const char* template, cJSON* data); +char* get_general_prompt(const char* label); +char* get_general_select_prompt(const char* label); + +#endif // OIDC_GETTEXT_H diff --git a/src/utils/prompt.c b/src/utils/prompting/prompt.c similarity index 96% rename from src/utils/prompt.c rename to src/utils/prompting/prompt.c index ba6d4df7..30b475bc 100644 --- a/src/utils/prompt.c +++ b/src/utils/prompting/prompt.c @@ -9,13 +9,13 @@ #include #include -#include "memory.h" -#include "oidc_error.h" -#include "printer.h" +#include "prompt_mode.h" #include "utils/file_io/file_io.h" #include "utils/listUtils.h" #include "utils/logger.h" -#include "utils/prompt_mode.h" +#include "utils/memory.h" +#include "utils/oidc_error.h" +#include "utils/printer.h" #include "utils/string/stringUtils.h" #include "utils/system_runner.h" #ifdef ANY_MSYS @@ -46,7 +46,7 @@ char* oidcPromptCmdWithList(const char* type, const char* title, } char* tmp = oidc_sprintf("%s \"%s\"", cmd, (char*)list_ats(inits, i)->val); if (tmp == NULL) { - logger(ERROR, oidc_serror()); + logger(ERROR, "%s", oidc_serror()); } else { secFree(cmd); cmd = tmp; @@ -56,8 +56,11 @@ char* oidcPromptCmdWithList(const char* type, const char* title, } void displayLinkGUI(const char* text, const char* link, const char* qr_path) { - char* cmd = oidcPromptCmd("link", "oidc-agent - Reauthentication required", - text, link, qr_path ?: "", PROMPT_DEFAULT_TIMEOUT); + char* escaped_text = escapeCharInStr(text, '"'); + char* cmd = oidcPromptCmd("link", "oidc-agent - Authentication required", + escaped_text, link ?: "", qr_path ?: "", + PROMPT_DEFAULT_TIMEOUT); + secFree(escaped_text); fireCommand(cmd); secFree(cmd); } @@ -378,4 +381,4 @@ char* promptMytokenConsentGUI(const char* base64html, const int timeout) { return NULL; } return out; -} \ No newline at end of file +} diff --git a/src/utils/prompt.h b/src/utils/prompting/prompt.h similarity index 100% rename from src/utils/prompt.h rename to src/utils/prompting/prompt.h diff --git a/src/utils/promptUtils.c b/src/utils/prompting/promptUtils.c similarity index 90% rename from src/utils/promptUtils.c rename to src/utils/prompting/promptUtils.c index 171e8eb7..9e42c14e 100644 --- a/src/utils/promptUtils.c +++ b/src/utils/prompting/promptUtils.c @@ -3,14 +3,16 @@ #include #include "defines/settings.h" +#include "getprompt.h" +#include "prompt.h" #include "utils/crypt/cryptUtils.h" #include "utils/crypt/gpg/gpg.h" #include "utils/file_io/file_io.h" #include "utils/file_io/oidc_file_io.h" +#include "utils/json.h" #include "utils/memory.h" #include "utils/oidc_error.h" #include "utils/printer.h" -#include "utils/prompt.h" #include "utils/string/stringUtils.h" #include "utils/system_runner.h" #ifdef __MSYS__ @@ -44,11 +46,11 @@ char* getEncryptionPasswordFor(const char* forWhat, const char* pw_cmd, const char* pw_file, const char* pw_env) { if (pw_env != NULL) { - #ifdef __MSYS__ +#ifdef __MSYS__ char* pass = getRegistryValue(pw_env); - #else +#else char* pass = oidc_strcopy(getenv(pw_env)); - #endif +#endif if (pass) { return pass; } @@ -65,29 +67,32 @@ char* getEncryptionPasswordFor(const char* forWhat, return pass; } } - char* encryptionPassword = NULL; + char* encryptionPassword = NULL; + cJSON* data = generateJSONObject("shortname", cJSON_String, forWhat); + char* prompt_text = getprompt(PROMPTTEMPLATE(PASSWORD), data); + secFreeJson(data); while (1) { - char* prompt_text = - oidc_sprintf("Enter encryption password for %s", forWhat); char* input = promptPassword(prompt_text, "Encryption password", suggestedPassword, CLI_PROMPT_VERBOSE); - secFree(prompt_text); if (strValid(suggestedPassword) && input && strequal(suggestedPassword, input)) { // use same encryption password secFree(input); encryptionPassword = oidc_strcopy(suggestedPassword); + secFree(prompt_text); return encryptionPassword; } else { - encryptionPassword = input; - char* confirm = - promptPassword("Confirm encryption Password", "Encryption password", - NULL, CLI_PROMPT_VERBOSE); + encryptionPassword = input; + char* prompt_text_2 = getprompt(PROMPTTEMPLATE(CONFIRM_PASSWORD), NULL); + char* confirm = promptPassword(prompt_text_2, "Encryption password", NULL, + CLI_PROMPT_VERBOSE); if (!strequal(encryptionPassword, confirm)) { printError("Encryption passwords did not match.\n"); secFree(confirm); + secFree(prompt_text_2); secFree(encryptionPassword); } else { secFree(confirm); + secFree(prompt_text); return encryptionPassword; } } @@ -101,11 +106,11 @@ char* getDecryptionPasswordFor(const char* forWhat, const char* pw_cmd, unsigned int max_tries = max_pass_tries == 0 ? MAX_PASS_TRIES : max_pass_tries; if (pw_env && (number_try == NULL || *number_try == 0)) { - #ifndef __MSYS__ +#ifndef __MSYS__ char* pass = oidc_strcopy(getenv(pw_env)); - #else +#else char* pass = getRegistryValue(pw_env); - #endif +#endif if (pass) { if (number_try) { (*number_try)++; diff --git a/src/utils/promptUtils.h b/src/utils/prompting/promptUtils.h similarity index 100% rename from src/utils/promptUtils.h rename to src/utils/prompting/promptUtils.h diff --git a/src/utils/prompt_mode.c b/src/utils/prompting/prompt_mode.c similarity index 50% rename from src/utils/prompt_mode.c rename to src/utils/prompting/prompt_mode.c index 5dc57eca..15233449 100644 --- a/src/utils/prompt_mode.c +++ b/src/utils/prompting/prompt_mode.c @@ -1,7 +1,9 @@ #include "prompt_mode.h" -unsigned char _prompt_mode; -unsigned char _pw_prompt_mode; +#include "utils/string/stringUtils.h" + +static unsigned char _prompt_mode; +static unsigned char _pw_prompt_mode; void set_prompt_mode(unsigned char mode) { _prompt_mode = mode; } void set_pw_prompt_mode(unsigned char mode) { _pw_prompt_mode = mode; } @@ -16,3 +18,14 @@ unsigned char pw_prompt_mode() { } return PROMPT_MODE_CLI; // Default, so PW prompts are always possible. } + +unsigned char parse_prompt_mode(const char* str) { + if (!strValid(str) || strcaseequal(str, "cli")) { + return PROMPT_MODE_CLI; + } else if (strcaseequal(str, "gui")) { + return PROMPT_MODE_GUI; + } else if (strcaseequal(str, "none")) { + return PROMPT_MODE_NONE; + } + return PROMPT_MODE_INVALID; +} \ No newline at end of file diff --git a/src/utils/prompt_mode.h b/src/utils/prompting/prompt_mode.h similarity index 84% rename from src/utils/prompt_mode.h rename to src/utils/prompting/prompt_mode.h index 198e24e0..9f40b122 100644 --- a/src/utils/prompt_mode.h +++ b/src/utils/prompting/prompt_mode.h @@ -1,8 +1,10 @@ #ifndef OIDC_PROMPT_MODE_H #define OIDC_PROMPT_MODE_H +#define PROMPT_MODE_NONE 0 #define PROMPT_MODE_CLI 1 #define PROMPT_MODE_GUI 2 +#define PROMPT_MODE_INVALID 255 /** set_prompt_mode sets the prompt mode and password prompt mode, if a * password prompt mode that is different from the normal prompt mode should @@ -14,5 +16,6 @@ void set_prompt_mode(unsigned char mode); void set_pw_prompt_mode(unsigned char mode); unsigned char prompt_mode(); unsigned char pw_prompt_mode(); +unsigned char parse_prompt_mode(const char* str); #endif // OIDC_PROMPT_MODE_H diff --git a/src/utils/prompting/templates/authenticate_cli.mustache b/src/utils/prompting/templates/authenticate_cli.mustache new file mode 100644 index 00000000..3f0439ac --- /dev/null +++ b/src/utils/prompting/templates/authenticate_cli.mustache @@ -0,0 +1,3 @@ +{{intro}} +To continue please open the following URL in {{#code}}a browser on any device {{#qr}}(or use the QR code) {{/qr}}and enter the following code{{/code}}{{^code}}your browser{{/code}}: +{{code}} \ No newline at end of file diff --git a/src/utils/prompting/templates/authenticate_gui.mustache b/src/utils/prompting/templates/authenticate_gui.mustache new file mode 100644 index 00000000..d71a2726 --- /dev/null +++ b/src/utils/prompting/templates/authenticate_gui.mustache @@ -0,0 +1,15 @@ +

Authenticate

+

+{{intro}} +To continue please open the following URL in {{#code}}a browser on any device {{#qr}}(or use the QR code) {{/qr}}and enter the following code{{/code}}{{^code}}your browser{{/code}}: +{{#code}}

{{.}}

{{/code}} +{{#url}} +

+ {{.}} +

+ +
+ +
+{{/url}} +

You need to close this window manually

\ No newline at end of file diff --git a/src/utils/prompting/templates/confirm_cli.mustache b/src/utils/prompting/templates/confirm_cli.mustache new file mode 100644 index 00000000..f2518e0f --- /dev/null +++ b/src/utils/prompting/templates/confirm_cli.mustache @@ -0,0 +1,3 @@ +{{#application-hint}}{{.}}{{/application-hint}}{{^application-hint}}An application{{/application-hint}} requests an {{token-type}} token for {{#issuer}}{{.}}{{/issuer}}{{^issuer}}{{shortname}}{{/issuer}}. +{{#id}}ID tokens should not be passed to other applications as authorization.{{/id}} +Do you want to allow the usage of {shortname}}? \ No newline at end of file diff --git a/src/utils/prompting/templates/confirm_gui.mustache b/src/utils/prompting/templates/confirm_gui.mustache new file mode 100644 index 00000000..08bdd764 --- /dev/null +++ b/src/utils/prompting/templates/confirm_gui.mustache @@ -0,0 +1,5 @@ +

Confirm

+

+{{#application-hint}}{{.}}{{/application-hint}}{{^application-hint}}An application{{/application-hint}} requests an {{token-type}} token for {{#issuer}}{{.}}{{/issuer}}{{^issuer}}{{shortname}}{{/issuer}}. +{{#id}}ID tokens should not be passed to other applications as authorization.{{/id}} +Do you want to allow the usage of {{shortname}}? diff --git a/src/utils/prompting/templates/confirm_password_cli.mustache b/src/utils/prompting/templates/confirm_password_cli.mustache new file mode 100644 index 00000000..05bed435 --- /dev/null +++ b/src/utils/prompting/templates/confirm_password_cli.mustache @@ -0,0 +1 @@ +Confirm encryption password \ No newline at end of file diff --git a/src/utils/prompting/templates/confirm_password_gui.mustache b/src/utils/prompting/templates/confirm_password_gui.mustache new file mode 100644 index 00000000..b758a09b --- /dev/null +++ b/src/utils/prompting/templates/confirm_password_gui.mustache @@ -0,0 +1,3 @@ +

Confirm Password

+

+Confirm encryption password: \ No newline at end of file diff --git a/src/utils/prompting/templates/gen.sh b/src/utils/prompting/templates/gen.sh new file mode 100755 index 00000000..f73a0af8 --- /dev/null +++ b/src/utils/prompting/templates/gen.sh @@ -0,0 +1,33 @@ +#!/bin/bash +truncate -s 0 templates.h + +function handle_file { + l=$1 + path_name="${l%.mustache}" + name="${path_name##*/}" + name="${name^^}" + #template=$(html-minifier --collapse-boolean-attributes --collapse-whitespace --minify-js true --minify-css true --quote-character \' $l | sed -e 's/\\/\\\\/g' | sed -e 's/"/\\"/g') + template=$(sed -e 's/^[[:space:]]*//;s/[[:space:]]*$//' $l | sed -e 's/\\/\\\\/g' | sed -e 's/"/\\"/g') + template=${template//$'\n'/\\n} + echo "#define PROMPTTEMPLATE_${name} \"${template}\"" >> templates.h +} + +( + echo "// This file is automatically generated. If any of the .mustache files is changed, rerun the generation." + echo "#ifndef PROMPTTEXT_TEMPLATES_H" + echo "#define PROMPTTEXT_TEMPLATES_H" + echo + echo '#include "wrapper/cjson.h"' + echo +) >> templates.h + +FILES=$(ls *.mustache) + +for l in ${FILES[@]}; do + handle_file $l +done + +( + echo + echo "#endif // PROMPTTEXT_TEMPLATES_H" +) >> templates.h diff --git a/src/utils/prompting/templates/link_identity_cli.mustache b/src/utils/prompting/templates/link_identity_cli.mustache new file mode 100644 index 00000000..9281cbb0 --- /dev/null +++ b/src/utils/prompting/templates/link_identity_cli.mustache @@ -0,0 +1,2 @@ +{{#application-hint}}{{.}}{{/application-hint}}{{^application-hint}}An application{{/application-hint}} requests an access token for {{issuer}}. +There currently is no identity configured for this issuer. Do you want to configure one now? \ No newline at end of file diff --git a/src/utils/prompting/templates/link_identity_gui.mustache b/src/utils/prompting/templates/link_identity_gui.mustache new file mode 100644 index 00000000..6c1c5481 --- /dev/null +++ b/src/utils/prompting/templates/link_identity_gui.mustache @@ -0,0 +1,5 @@ +

Link Identity

+

+{{#application-hint}}{{.}}{{/application-hint}}{{^application-hint}}An application{{/application-hint}} requests an access token for {{issuer}}. +

+There currently is no identity configured for this issuer. Do you want to configure one now? \ No newline at end of file diff --git a/src/utils/prompting/templates/password_cli.mustache b/src/utils/prompting/templates/password_cli.mustache new file mode 100644 index 00000000..22e8e15e --- /dev/null +++ b/src/utils/prompting/templates/password_cli.mustache @@ -0,0 +1 @@ +Enter encryption password for {{shortname}} \ No newline at end of file diff --git a/src/utils/prompting/templates/password_gui.mustache b/src/utils/prompting/templates/password_gui.mustache new file mode 100644 index 00000000..1b8120e7 --- /dev/null +++ b/src/utils/prompting/templates/password_gui.mustache @@ -0,0 +1,3 @@ +

Enter Password

+

+Enter encryption password for {{shortname}}: \ No newline at end of file diff --git a/src/utils/prompting/templates/shortname_cli.mustache b/src/utils/prompting/templates/shortname_cli.mustache new file mode 100644 index 00000000..c277588a --- /dev/null +++ b/src/utils/prompting/templates/shortname_cli.mustache @@ -0,0 +1,5 @@ +{{#exists}}An account with that shortname is already configured. +Please choose another shortname. + +{{/exists}} +Enter short name for the account to configure \ No newline at end of file diff --git a/src/utils/prompting/templates/shortname_gui.mustache b/src/utils/prompting/templates/shortname_gui.mustache new file mode 100644 index 00000000..c74c7462 --- /dev/null +++ b/src/utils/prompting/templates/shortname_gui.mustache @@ -0,0 +1,6 @@ +

Configure account

+

+{{#exists}}An account with that shortname is already configured. +Please choose another shortname. +

{{/exists}} +Enter short name for the account to configure: \ No newline at end of file diff --git a/src/utils/prompting/templates/templates.h b/src/utils/prompting/templates/templates.h new file mode 100644 index 00000000..c7839f4b --- /dev/null +++ b/src/utils/prompting/templates/templates.h @@ -0,0 +1,26 @@ +// This file is automatically generated. If any of the .mustache files is changed, rerun the generation. +#ifndef PROMPTTEXT_TEMPLATES_H +#define PROMPTTEXT_TEMPLATES_H + +#include "wrapper/cjson.h" + +#define PROMPTTEMPLATE_AUTHENTICATE_CLI "{{intro}}\nTo continue please open the following URL in {{#code}}a browser on any device {{#qr}}(or use the QR code) {{/qr}}and enter the following code{{/code}}{{^code}}your browser{{/code}}:\n{{code}}" +#define PROMPTTEMPLATE_AUTHENTICATE_GUI "

Authenticate

\n

\n{{intro}}\nTo continue please open the following URL in {{#code}}a browser on any device {{#qr}}(or use the QR code) {{/qr}}and enter the following code{{/code}}{{^code}}your browser{{/code}}:\n{{#code}}

{{.}}

{{/code}}\n{{#url}}\n

\n{{.}}\n

\n\n
\n\n
\n{{/url}}\n

You need to close this window manually

" +#define PROMPTTEMPLATE_CONFIRM_CLI "{{#application-hint}}{{.}}{{/application-hint}}{{^application-hint}}An application{{/application-hint}} requests an {{token-type}} token for {{#issuer}}{{.}}{{/issuer}}{{^issuer}}{{shortname}}{{/issuer}}.\n{{#id}}ID tokens should not be passed to other applications as authorization.{{/id}}\nDo you want to allow the usage of {shortname}}?" +#define PROMPTTEMPLATE_CONFIRM_GUI "

Confirm

\n

\n{{#application-hint}}{{.}}{{/application-hint}}{{^application-hint}}An application{{/application-hint}} requests an {{token-type}} token for {{#issuer}}{{.}}{{/issuer}}{{^issuer}}{{shortname}}{{/issuer}}.\n{{#id}}ID tokens should not be passed to other applications as authorization.{{/id}}\nDo you want to allow the usage of {{shortname}}?" +#define PROMPTTEMPLATE_CONFIRM_PASSWORD_CLI "Confirm encryption password" +#define PROMPTTEMPLATE_CONFIRM_PASSWORD_GUI "

Confirm Password

\n

\nConfirm encryption password:" +#define PROMPTTEMPLATE_LINK_IDENTITY_CLI "{{#application-hint}}{{.}}{{/application-hint}}{{^application-hint}}An application{{/application-hint}} requests an access token for {{issuer}}.\nThere currently is no identity configured for this issuer. Do you want to configure one now?" +#define PROMPTTEMPLATE_LINK_IDENTITY_GUI "

Link Identity

\n

\n{{#application-hint}}{{.}}{{/application-hint}}{{^application-hint}}An application{{/application-hint}} requests an access token for {{issuer}}.\n

\nThere currently is no identity configured for this issuer. Do you want to configure one now?" +#define PROMPTTEMPLATE_PASSWORD_CLI "Enter encryption password for {{shortname}}" +#define PROMPTTEMPLATE_PASSWORD_GUI "

Enter Password

\n

\nEnter encryption password for {{shortname}}:" +#define PROMPTTEMPLATE_SHORTNAME_CLI "{{#exists}}An account with that shortname is already configured.\nPlease choose another shortname.\n\n{{/exists}}\nEnter short name for the account to configure" +#define PROMPTTEMPLATE_SHORTNAME_GUI "

Configure account

\n

\n{{#exists}}An account with that shortname is already configured.\nPlease choose another shortname.\n

{{/exists}}\nEnter short name for the account to configure:" +#define PROMPTTEMPLATE_UNLOCK_ACCOUNT_CLI "{{#application-hint}}{{.}}{{/application-hint}}{{^application-hint}}An application{{/application-hint}} requests an access token for {{shortname}}, which is currently not loaded.\nEnter the encryption password to unlock account {{shortname}}" +#define PROMPTTEMPLATE_UNLOCK_ACCOUNT_GUI "

Unlock Identity

\n

\n{{#application-hint}}{{.}}{{/application-hint}}{{^application-hint}}An application{{/application-hint}} requests an access token for {{shortname}}, which is currently not loaded.\nEnter the encryption password to unlock account {{shortname}}." +#define PROMPTTEMPLATE_UNLOCK_ACCOUNT_ISSUER_CLI "{{#application-hint}}{{.}}{{/application-hint}}{{^application-hint}}An application{{/application-hint}} requests an access token for {{issuer}}, but this provider is currently not loaded.\nEnter the encryption password to unlock account {{shortname}}" +#define PROMPTTEMPLATE_UNLOCK_ACCOUNT_ISSUER_GUI "

Unlock Identity

\n

\n{{#application-hint}}{{.}}{{/application-hint}}{{^application-hint}}An application{{/application-hint}} requests an access token for {{issuer}}, but this provider is currently not loaded.\nEnter the encryption password to unlock account {{shortname}}." +#define PROMPTTEMPLATE_UPDATE_ACCOUNT_CLI "oidc-agent needs to update the account config for {{shortname}}.\nPlease enter the encryption password for {{shortname}}" +#define PROMPTTEMPLATE_UPDATE_ACCOUNT_GUI "

Update Account Config

\n

\noidc-agent needs to update the account config for {{shortname}}.
\nPlease enter the encryption password for {{shortname}}." + +#endif // PROMPTTEXT_TEMPLATES_H diff --git a/src/utils/prompting/templates/unlock_account_cli.mustache b/src/utils/prompting/templates/unlock_account_cli.mustache new file mode 100644 index 00000000..49310313 --- /dev/null +++ b/src/utils/prompting/templates/unlock_account_cli.mustache @@ -0,0 +1,2 @@ +{{#application-hint}}{{.}}{{/application-hint}}{{^application-hint}}An application{{/application-hint}} requests an access token for {{shortname}}, which is currently not loaded. +Enter the encryption password to unlock account {{shortname}} \ No newline at end of file diff --git a/src/utils/prompting/templates/unlock_account_gui.mustache b/src/utils/prompting/templates/unlock_account_gui.mustache new file mode 100644 index 00000000..e1ef78ee --- /dev/null +++ b/src/utils/prompting/templates/unlock_account_gui.mustache @@ -0,0 +1,4 @@ +

Unlock Identity

+

+{{#application-hint}}{{.}}{{/application-hint}}{{^application-hint}}An application{{/application-hint}} requests an access token for {{shortname}}, which is currently not loaded. +Enter the encryption password to unlock account {{shortname}}. diff --git a/src/utils/prompting/templates/unlock_account_issuer_cli.mustache b/src/utils/prompting/templates/unlock_account_issuer_cli.mustache new file mode 100644 index 00000000..663e79f0 --- /dev/null +++ b/src/utils/prompting/templates/unlock_account_issuer_cli.mustache @@ -0,0 +1,2 @@ +{{#application-hint}}{{.}}{{/application-hint}}{{^application-hint}}An application{{/application-hint}} requests an access token for {{issuer}}, but this provider is currently not loaded. +Enter the encryption password to unlock account {{shortname}} \ No newline at end of file diff --git a/src/utils/prompting/templates/unlock_account_issuer_gui.mustache b/src/utils/prompting/templates/unlock_account_issuer_gui.mustache new file mode 100644 index 00000000..9b99ff5d --- /dev/null +++ b/src/utils/prompting/templates/unlock_account_issuer_gui.mustache @@ -0,0 +1,4 @@ +

Unlock Identity

+

+{{#application-hint}}{{.}}{{/application-hint}}{{^application-hint}}An application{{/application-hint}} requests an access token for {{issuer}}, but this provider is currently not loaded. +Enter the encryption password to unlock account {{shortname}}. diff --git a/src/utils/prompting/templates/update_account_cli.mustache b/src/utils/prompting/templates/update_account_cli.mustache new file mode 100644 index 00000000..c627a3f6 --- /dev/null +++ b/src/utils/prompting/templates/update_account_cli.mustache @@ -0,0 +1,2 @@ +oidc-agent needs to update the account config for {{shortname}}. +Please enter the encryption password for {{shortname}} \ No newline at end of file diff --git a/src/utils/prompting/templates/update_account_gui.mustache b/src/utils/prompting/templates/update_account_gui.mustache new file mode 100644 index 00000000..044b07a8 --- /dev/null +++ b/src/utils/prompting/templates/update_account_gui.mustache @@ -0,0 +1,4 @@ +

Update Account Config

+

+oidc-agent needs to update the account config for {{shortname}}.
+Please enter the encryption password for {{shortname}}. \ No newline at end of file diff --git a/src/utils/pubClientInfos.c b/src/utils/pubClientInfos.c deleted file mode 100644 index df4be73d..00000000 --- a/src/utils/pubClientInfos.c +++ /dev/null @@ -1,79 +0,0 @@ -#include "pubClientInfos.h" - -#include - -#include "account/issuer_helper.h" -#include "defines/msys.h" -#include "defines/settings.h" -#include "utils/file_io/file_io.h" -#include "utils/file_io/oidc_file_io.h" -#include "utils/listUtils.h" -#include "utils/memory.h" -#include "utils/string/stringUtils.h" - -void secFreePubClientInfos(struct pubClientInfos* p) { - if (p == NULL) { - return; - } - secFree(p->client_id); - secFree(p->client_secret); - secFree(p->scope); - secFree(p); -} - -struct pubClientInfos* _getPubClientInfosFromList(list_t* lines, - const char* issuer) { - if (lines == NULL) { - return NULL; - } - list_node_t* node; - list_iterator_t* it = list_iterator_new(lines, LIST_HEAD); - while ((node = list_iterator_next(it))) { - char* client = strtok(node->val, "@"); - char* iss = strtok(NULL, "@"); - char* scope = strtok(NULL, "@"); - if (iss == NULL) { - continue; - } - if (compIssuerUrls(issuer, iss)) { - char* client_id = strtok(client, ":"); - char* client_secret = strtok(NULL, ":"); - struct pubClientInfos* infos = secAlloc(sizeof(struct pubClientInfos)); - infos->client_id = oidc_strcopy(client_id); - infos->client_secret = oidc_strcopy(client_secret); - infos->scope = oidc_strcopy(scope); - list_iterator_destroy(it); - return infos; - } - } - list_iterator_destroy(it); - return NULL; -} - -struct pubClientInfos* getPubClientInfos(const char* issuer) { - list_t* pubClientLines = getLinesFromFileWithoutComments( -#ifdef ANY_MSYS - ETC_PUBCLIENTS_CONFIG_FILE() -#else - ETC_PUBCLIENTS_CONFIG_FILE -#endif - ); - struct pubClientInfos* infos = - _getPubClientInfosFromList(pubClientLines, issuer); - secFreeList(pubClientLines); - if (infos != NULL) { - return infos; - } - pubClientLines = getLinesFromOidcFileWithoutComments(PUBCLIENTS_FILENAME); - infos = _getPubClientInfosFromList(pubClientLines, issuer); - secFreeList(pubClientLines); - return infos; -} - -list_t* defaultRedirectURIs() { - list_t* redirect_uris = - createList(0, "http://localhost:8080", "http://localhost:4242", - "http://localhost:43985", NULL); - redirect_uris->match = (matchFunction)strequal; - return redirect_uris; -} diff --git a/src/utils/pubClientInfos.h b/src/utils/pubClientInfos.h deleted file mode 100644 index 36e59ab5..00000000 --- a/src/utils/pubClientInfos.h +++ /dev/null @@ -1,16 +0,0 @@ -#ifndef PUBCLIENT_INFOS_H -#define PUBCLIENT_INFOS_H - -#include "wrapper/list.h" - -struct pubClientInfos { - char* client_id; - char* client_secret; - char* scope; -}; - -void secFreePubClientInfos(struct pubClientInfos* p); -struct pubClientInfos* getPubClientInfos(const char* issuer); -list_t* defaultRedirectURIs(); - -#endif /* PUBCLIENT_INFOS_H */ diff --git a/src/utils/string/stringUtils.c b/src/utils/string/stringUtils.c index a96dc310..8da6789e 100644 --- a/src/utils/string/stringUtils.c +++ b/src/utils/string/stringUtils.c @@ -9,9 +9,9 @@ #include #include "defines/msys.h" +#include "strptime.h" #include "utils/memory.h" #include "utils/oidc_error.h" -#include "strptime.h" /** @fn int strValid(const char* c) * @brief checks if a string contains a valid value, meaning it is not empty, @@ -342,11 +342,21 @@ unsigned char strToUChar(const char* str) { oidc_setArgNullFuncError(__func__); return 0; } + if (strcaseequal("false", str)) { + return 0; + } + if (strcaseequal("true", str)) { + return 1; + } unsigned char c = 0; sscanf(str, "%hhu", &c); return c; } +unsigned char strToBit(const char* str) { + return strToUChar(str)!=0; +} + unsigned short strToUShort(const char* str) { if (str == NULL) { oidc_setArgNullFuncError(__func__); diff --git a/src/utils/string/stringUtils.h b/src/utils/string/stringUtils.h index 8f92c34b..983d2a0a 100644 --- a/src/utils/string/stringUtils.h +++ b/src/utils/string/stringUtils.h @@ -43,6 +43,7 @@ long strToLong(const char* str); unsigned long strToULong(const char* str); int strToInt(const char* str); unsigned char strToUChar(const char* str); +unsigned char strToBit(const char* str); unsigned short strToUShort(const char* str); char* repeatChar(char c, size_t n); diff --git a/src/utils/system_runner.c b/src/utils/system_runner.c index feaf523f..936e5287 100644 --- a/src/utils/system_runner.c +++ b/src/utils/system_runner.c @@ -51,7 +51,7 @@ static struct pid { struct pid* next; FILE* fp; pid_t pid; -} * pidlist; +}* pidlist; FILE* win_popen(const char* program, const char* type) { struct pid* cur; @@ -134,7 +134,7 @@ char* getOutputFromCommand(const char* cmd) { if (fp == NULL) { oidc_setErrnoError(); logger(ERROR, "Failed to execute command: %s", cmd); - logger(ERROR, oidc_serror()); + logger(ERROR, "%s", oidc_serror()); return NULL; } char* ret = readFILE(fp); @@ -143,6 +143,7 @@ char* getOutputFromCommand(const char* cmd) { } void fireCommand(const char* cmd) { + logger(DEBUG, "Fire command: %s", cmd); pid_t pid = fork(); if (pid == -1) { logger(ERROR, "fork %m"); diff --git a/src/utils/uriUtils.c b/src/utils/uriUtils.c index f03f400d..76ceda26 100644 --- a/src/utils/uriUtils.c +++ b/src/utils/uriUtils.c @@ -59,6 +59,26 @@ char* getBaseUri(const char* uri) { return base; } +char* getTopHost(const char* uri) { + if (uri == NULL) { + oidc_setArgNullFuncError(__func__); + return NULL; + } + char* tmp = oidc_strcopy(uri); + char* b = strstr(tmp, "://") + 3; + char* e = strchr(b, '/'); + *e = '\0'; + e = strrchr(b, '.'); + *e = '\0'; + char* t = strrchr(b, '.'); + if (t!=NULL) { + b=t+1; + } + char* extracted = oidc_strcopy(b ); + secFree(tmp); + return extracted; +} + struct codeState codeStateFromURI(const char* uri) { if (uri == NULL) { oidc_setArgNullFuncError(__func__); diff --git a/src/utils/uriUtils.h b/src/utils/uriUtils.h index 99b1f869..792abe0a 100644 --- a/src/utils/uriUtils.h +++ b/src/utils/uriUtils.h @@ -15,6 +15,7 @@ void secFreeCodeState(struct codeState cs); char* findCustomSchemeUri(list_t* uris); char* extractParameterValueFromUri(const char* uri, const char* parameter); char* getBaseUri(const char* uri); +char* getTopHost(const char* uri); oidc_error_t checkRedirectUrisForErrors(list_t* redirect_uris); #endif // OIDC_URIUTILS_H