From 906201c2b5221e42e33cbf57844c254993c0bc13 Mon Sep 17 00:00:00 2001 From: Tom Scavo Date: Wed, 21 Dec 2016 16:56:09 -0500 Subject: [PATCH] Initial commit of probe_saml_idps.sh --- bin/probe_saml_idps.sh | 690 +++++++++++++++++++++++++++++++++++++++++ install.sh | 1 + 2 files changed, 691 insertions(+) create mode 100755 bin/probe_saml_idps.sh diff --git a/bin/probe_saml_idps.sh b/bin/probe_saml_idps.sh new file mode 100755 index 0000000..a75fe25 --- /dev/null +++ b/bin/probe_saml_idps.sh @@ -0,0 +1,690 @@ +#!/bin/bash + +####################################################################### +# Copyright 2015--2016 Internet2 +# +# 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. +####################################################################### + +script_version="0.6" +user_agent_string="SAML IdP Probe ${script_version}" + +####################################################################### +# help message +####################################################################### + +display_help () { +/bin/cat <<- HELP_MSG + ${user_agent_string} + + Given a list of identifiers, assumed to be IdP entityIDs, determine + which of those identifiers correspond to live SAML IdP deployments. + This script probes SAML2 IdP deployments only. + + Usage: ${0##*/} [-hvq] [-d OUT_DIR] [-t CONNECT_TIME [-m MAX_TIME]] [-r MAX_REDIRS] [ID ...] + + The script optionally takes a sequence of identifiers on the command + line. If none are given, the script takes its input from stdin. + + The script iterates over all input identifiers. For each identifier, + if the corresponding entity is a SAML2 IdP, the script probes the + HTTP-Redirect endpoint, the HTTP-POST endpoint, and the + HTTP-POST-SimpleSign endpoint at that IdP. In other words, this + script probes up to three (3) browser-facing SSO endpoints at the + IdP. + + Options: + -h Display this message + -v Write verbose messages to stdout + -q Run quietly (i.e., write no messages to stdout) + -t Time (in secs) to connect to the host + -m Maximum time (in secs) of a complete probe + -r Maximum number of HTTP redirects followed + -d Path to an output directory + + Option -h is mutually exclusive of all other options. Options + -q and -v are mutually exclusive of each other. Options -u and -f + are mutually exclusive of each other as well. + + The argument of the -t option is the TCP connect time, that is, + the maximum time (in secs) allotted to the TCP connection. Note + that the TCP connect time includes the time it takes to do a + DNS name lookup. Since the latter is unconstrained, it may + consume all available TCP connect time. Thus the TCP connect + time should be kept small (on the order of a few seconds) since + larger values will slow this script considerably. + + The argument of the -m option is the maximum total time (in secs) + allotted to each probe. A reasonable value is a few seconds + beyond the TCP connect time. Any value less than the TCP connect + time causes the script to immediately fail. + + CONFIG + + The script reads a file of config parameters. The script loads the + config file from the following file location: + + $config_file_default + + As a result of reading the config file, the following config + parameters are initialized: + + MDQ_BASE_URL + SAML2_SP_ENTITY_ID + SAML2_SP_ACS_URL + SAML2_SP_ACS_BINDING + SAML1_SP_ENTITY_ID + SAML1_SP_ACS_URL + SAML1_SP_ACS_BINDING + + The MDQ_BASE_URL is the base URL of a Metadata Query Server + (i.e., a server that conforms to the Metadata Query Protocol). + The base URL is used to construct an MDQ request URL, which the + script uses to request entity metadata just-in-time. + + The three SAML2_SP parameters define a SAML2 SP, that is, an SP + with one or more SAML2 browser-facing endpoints in metadata. The + SAML AuthnRequest transmitted to the IdP contains the values of + these parameters. Note: An IdP reacts differently to requests + from different SPs. Changing the values of these parameters may + produce different probe results. + + Similarly, the three SAML1_SP parameters define a SAML1 SP, that + is, an SP with a SAML1 browser-facing endpoint in metadata. (Any + given SP may support both SAML2 and SAML1, in which case the + SAML1_SP_ENTITY_ID parameter may be identical to the + SAML2_SP_ENTITY_ID parameter.) The script probes SAML1 endpoints + if the -a option is given on the command line. Omit that option + to probe SAML2 endpoints only. + + STDOUT + + By default, the script outputs an abbreviated log to stdout (but + this may be suppressed by use of the -q option). A line of + standard output has the following space-delimited fields: + + 1) code: a curl exit code + 2) output: a curl output string + 3) location: the URL of the probed SAML2 HTTP-Redirect endpoint + 4) liveness: IdP liveness indicator + + See the curl man page (http://linux.die.net/man/1/curl) for a + brief description of possible exit codes. + + The output string has the following format: + + response:999;dns:9.999;tcp:9.999;ssl:9.999;total:9.999 + + The response in the output string is the HTTP response code of the + probed web server. If the probe does not complete, the HTTP response + will be 000. The remaining four values in the output string are times + (in secs) computed by curl: + + dns is the elapsed time up to and including the DNS lookup + (curl time_namelookup variable) + tcp is the elapsed time up to and including the TCP connection + (curl time_connect variable) + ssl is the elapsed time up to and including the SSL exchange + (curl time_appconnect variable) (only curl 7.19.0 and later) + total is the total elapsed time of the probe + (curl time_total variable) + + See the curl man page (curl --write-out option) for detailed + explanations of these timings. + + The location is the actual URL probed by this script. It is + an HTTP-Redirect endpoint location in metadata. + + The IdP liveness indicator takes on one of four values: DEAD, + UNRESPONSIVE, INDETERMINATE, or SUCCESS. By definition, a probe + succeeds (SUCCESS) if its exit code is 0. For our purposes, a + probe completely fails (DEAD) if its exit code is either 6 or 7. + (Exit code 6 indicates a DNS lookup failure while code 7 means + the host is unreachable on the network.) A probe that times out + (exit code 28) is labeled as UNRESPONSIVE. All other exit codes + are regarded as INDETERMINATE. + + FILES + + The script writes a number of output files if (and only if) the + -d option is specified on the command line. The output files are + written to the given OUT_DIR. + + ${NO_SAML2_HTTP_ENDPOINT_FILENAME} + + A list of IdPs that do not expose a suitable SAML2 HTTP endpoint + location in metadata. A suitable endpoint supports one of the + following SAML2 HTTP bindings: HTTP-Redirect, HTTP-POST, or + HTTP-POST-SimpleSign. An IdP that supports SAML1 only will + necessarily appear on this list, and will therefore not be probed. + + A line in the output file has the following space-delimited fields: + + 1) entityID: the entityID of the IdP + 2) registrarID: the registrar ID + + The entityID is the name of the IdP. An entityID is an arbitrary URI, + as given by the entityID XML attribute on the + element in SAML metadata. + + The registrarID is the name of the registrar that registered the IdP + metadata in the first place. By convention, a registrar ID is an + arbitrary URI, as given by the registrationAuthority XML attribute + on the element in SAML metadata. Since the + latter element is optional in metadata, this field may be blank in + the log file (which is why it is always the last field on any + output line). + + ${IDP_LOG_FILENAME} + + A log of each probe. Each line records the result of the probe of + a single SAML IdP. A line in the log file has the following + space-delimited fields: + + 1) code: a curl exit code + 2) output: a curl output string + 3) location: a SAML2 HTTP-Redirect endpoint location + 4) entityID: the entityID of the Shibboleth IdP + 5) registrarID: the registrar ID + + The code, output, and statusURL fields are the same as those printed + to stdout. + + The location, entityID, and registrarID fields are the same as in the + previous output file. + + Examples: ${0##*/} -h + ${0##*/} \$id + ${0##*/} -t ${connect_timeout_default} \$id + cat \$id_file | ${0##*/} -v -t 4 + ${0##*/} -q -f /path/to/md_file.xml \$id1 \$id2 \$id3 + + Note that the second and third examples above behave identically. +HELP_MSG +} + +####################################################################### +# Bootstrap +####################################################################### + +script_bin=${0%/*} # equivalent to dirname $0 +script_name=${0##*/} # equivalent to basename $0 + +# determine the source lib directory +if [ -z "$LIB_DIR" ]; then + echo "ERROR: $script_name requires env var LIB_DIR" >&2 + exit 2 +fi +if [ ! -d "$LIB_DIR" ]; then + echo "ERROR: $script_name: LIB_DIR does not exist: $LIB_DIR" >&2 + exit 2 +fi + +# library filenames (always list command_paths first) +LIB_FILENAMES="command_paths.sh +compatible_mktemp.sh +http_tools.sh +md_tools.sh +saml_tools.sh +config_tools.sh" + +# source lib files +for lib_filename in $LIB_FILENAMES; do + lib_file="$LIB_DIR/$lib_filename" + if [ ! -f "$lib_file" ]; then + echo "ERROR: $script_name: lib file does not exist: $lib_file" >&2 + exit 2 + fi + source "$lib_file" + status_code=$? + if [ $status_code -ne 0 ]; then + echo "ERROR: $script_name failed to source lib file ($status_code) $lib_file" >&2 + exit 2 + fi +done + +# basic curl defaults +connect_timeout_default=2; max_redirs_default=7 + +# default binding URIs +binding_uris_default="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" + +# default config file +config_file_default="${script_bin}/.config_saml_idp_probe.sh" + +# output filenames +NO_SAML2_HTTP_ENDPOINT_FILENAME="idps-no-saml2-http-redirect-endpoint.txt" +IDP_LOG_FILENAME="idps-saml-log.txt" +IDP_NAMES_FILENAME="idp-names.txt" +ERROR_LOG_FILENAME="error-log.txt" +COMPATIBILITY_SCRIPT_FILENAME="compatibility.sh" + +####################################################################### +# Process command-line options and arguments +####################################################################### + +help_mode=false; quiet_mode=false; verbose_mode=false +local_opts=; curl_opts= +connect_timeout=; max_time=; max_redirs= +binding_uris="$binding_uris_default" +while getopts ":hqvat:m:r:d:" opt; do + case $opt in + h) + help_mode=true + ;; + q) + quiet_mode=true + verbose_mode=false + #local_opts="$local_opts -$opt" + ;; + v) + quiet_mode=false + verbose_mode=true + local_opts="$local_opts -$opt" + ;; + a) + binding_uris="$binding_uris urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST-SimpleSign" + binding_uris="$binding_uris urn:mace:shibboleth:1.0:profiles:AuthnRequest" + ;; + t) + connect_timeout="$OPTARG" + curl_opts="$curl_opts -t $OPTARG" + ;; + m) + max_time="$OPTARG" + curl_opts="$curl_opts -m $OPTARG" + ;; + r) + max_redirs="$OPTARG" + curl_opts="$curl_opts -r $OPTARG" + ;; + d) + OUT_DIR="$OPTARG" + ;; + \?) + echo "ERROR: $script_name: Unrecognized option: -$OPTARG" >&2 + exit 2 + ;; + :) + echo "ERROR: $script_name: Option -$OPTARG requires an argument" >&2 + exit 2 + ;; + esac +done + +if $help_mode; then + display_help + exit 0 +fi + +# redirect stdout to the bit bucket (stderr below) +$quiet_mode && exec 1>/dev/null + +# determine the output directory +if [ -z "$OUT_DIR" ]; then + DO_NOT_PRINT_FILES=true + $verbose_mode && printf "$script_name not printing output files\n" + # redirect stderr to the bit bucket + $quiet_mode && exec 2>/dev/null +else + DO_NOT_PRINT_FILES=false + # create the dir if necessary + if [ -d "$OUT_DIR" ]; then + $verbose_mode && printf "$script_name using output dir: %s\n" "$OUT_DIR" + else + $verbose_mode && printf "$script_name creating output dir: %s\n" "$OUT_DIR" + /bin/mkdir "$OUT_DIR" + exit_status=$? + if [ $exit_status -ne 0 ]; then + echo "ERROR: $script_name failed to create out dir ($exit_status) $OUT_DIR" >&2 + exit 2 + fi + fi +fi + +# check consistency of timeout options +if [ -n "$max_time" -a -z "$connect_timeout" ]; then + echo "ERROR: $script_name: the -m option requires the presence of the -t option" >&2 + exit 2 +fi + +# set default connect timeout if necessary +if [ -z "$connect_timeout" ]; then + connect_timeout=$connect_timeout_default + curl_opts="$curl_opts -t $connect_timeout" +else + if [ "$connect_timeout" -le 0 ] ; then + echo "ERROR: $script_name: connect timeout ($connect_timeout) must be a positive integer" >&2 + exit 2 + fi +fi + +# compute max time if necessary +if [ -z "$max_time" ]; then + max_time=$(( connect_timeout + 2 )) + curl_opts="$curl_opts -m $max_time" +else + if [ "$max_time" -le "$connect_timeout" ]; then + echo "ERROR: $script_name: max time ($max_time) must be greater than the connect timeout ($connect_timeout)" >&2 + exit 2 + fi +fi + +# check maximum number of redirects +if [ -z "$max_redirs" ]; then + max_redirs=$max_redirs_default + curl_opts="$curl_opts -r $max_redirs" +fi + +if $verbose_mode; then + printf "$script_name using connect timeout: %d secs\n" $connect_timeout + printf "$script_name using max time: %d secs\n" $max_time + printf "$script_name using max redirects: %d\n" $max_redirs +fi + +config_file="$config_file_default" +$verbose_mode && echo "$script_name using config file $config_file" + +##################################################################### +# Initialization +##################################################################### + +# report bootstrap operations +if $verbose_mode; then + printf "$script_name using source lib directory: %s\n" "$LIB_DIR" + for lib_filename in $LIB_FILENAMES; do + lib_file="$LIB_DIR/$lib_filename" + printf "$script_name sourcing lib file: %s\n" "$lib_file" + done +fi + +# determine temporary directory +if [ -n "$TMP_DIR" ] && [ -d "$TMP_DIR" ]; then + $verbose_mode && printf "$script_name using existing temporary dir: %s\n" "$TMP_DIR" + # use existing temporary directory (remove trailing slash) + tmp_dir="${TMP_DIR%%/}/probe_saml_idps_$$" +elif [ -n "$TMPDIR" ] && [ -d "$TMPDIR" ]; then + $verbose_mode && printf "$script_name using system temporary dir: %s\n" "$TMPDIR" + # use system temporary directory (remove trailing slash) + tmp_dir="${TMPDIR%%/}/probe_saml_idps_$$" +else + # create temporary directory + new_dir="$( make_temp_file -d )" + if [ ! -d "$new_dir" ] ; then + printf "ERROR: $script_name unable to create temporary dir\n" >&2 + exit 2 + fi + $verbose_mode && printf "$script_name using new temporary dir: %s\n" "$new_dir" + # use new temporary directory (remove trailing slash) + tmp_dir="${new_dir%%/}/probe_saml_idps_$$" +fi + +# every run of this script gets its own subdir +if [ -d "$tmp_dir" ]; then + echo "ERROR: $script_name: directory already exists: $tmp_dir" >&2 + exit 2 +fi + +# create temporary directory if necessary +$verbose_mode && printf "$script_name creating temporary subdir: %s\n" "$tmp_dir" +/bin/mkdir "$tmp_dir" +status_code=$? +if [ $status_code -ne 0 ]; then + echo "ERROR: $script_name failed to create tmp dir ($status_code) $tmp_dir" >&2 + exit 2 +fi + +# temp file TODO: store each response separately? +HTTP_RESPONSE_FILE="${tmp_dir}/http_response.txt" + +# read the input into a temporary file +IN_FILE="${tmp_dir}/tmp_entityids_in_$$.txt" +shift $(( OPTIND - 1 )) +if [ "$#" -gt 0 ]; then + # read input from the command line + while (( "$#" )); do + # copy command-line arg into the temp file + echo "$1" >> "$IN_FILE" + shift + done +else + # read input from stdin + /bin/cat - > "$IN_FILE" +fi +$verbose_mode && printf "$script_name processing temp input file: %s\n" "$IN_FILE" + +# load config file +$verbose_mode && echo "$script_name loading config file $config_file" +load_config $local_opts "$config_file" +status_code=$? +if [ $status_code -ne 0 ]; then + echo "ERROR: $script_name failed to load $config_file" >&2 + exit 2 +fi + +# validate config parameters +if [ -z "$MDQ_BASE_URL" ]; then + echo "ERROR: $script_name requires config param MDQ_BASE_URL" >&2 + exit 2 +fi +if [ -z "$SAML2_SP_ENTITY_ID" ]; then + echo "ERROR: $script_name requires config param SAML2_SP_ENTITY_ID" >&2 + exit 2 +fi +if [ -z "$SAML2_SP_ACS_URL" ]; then + echo "ERROR: $script_name requires config param SAML2_SP_ACS_URL" >&2 + exit 2 +fi +if [ -z "$SAML2_SP_ACS_BINDING" ]; then + echo "ERROR: $script_name requires config param SAML2_SP_ACS_BINDING" >&2 + exit 2 +fi +if [ -z "$SAML1_SP_ENTITY_ID" ]; then + echo "ERROR: $script_name requires config param SAML1_SP_ENTITY_ID" >&2 + exit 2 +fi +if [ -z "$SAML1_SP_ACS_URL" ]; then + echo "ERROR: $script_name requires config param SAML1_SP_ACS_URL" >&2 + exit 2 +fi +if [ -z "$SAML1_SP_ACS_BINDING" ]; then + echo "ERROR: $script_name requires config param SAML1_SP_ACS_BINDING" >&2 + exit 2 +fi + +##################################################################### +# Helper functions +##################################################################### + +init_out_files () { + $DO_NOT_PRINT_FILES && return + + # output files + NO_SAML2_HTTP_ENDPOINT_FILE="$OUT_DIR/$NO_SAML2_HTTP_ENDPOINT_FILENAME" + IDP_LOG_FILE="$OUT_DIR/$IDP_LOG_FILENAME" + IDP_NAMES_FILE="$OUT_DIR/$IDP_NAMES_FILENAME" + ERROR_LOG_FILE="$OUT_DIR/$ERROR_LOG_FILENAME" + COMPATIBILITY_SCRIPT_FILE="$OUT_DIR/$COMPATIBILITY_SCRIPT_FILENAME" + + # clean up from last time if necessary + /bin/rm -f "$NO_SAML2_HTTP_ENDPOINT_FILE" + /bin/rm -f "$IDP_LOG_FILE" + /bin/rm -f "$IDP_NAMES_FILE" + /bin/rm -f "$ERROR_LOG_FILE" + + # redirect stderr to a file + if $quiet_mode; then + $_TOUCH "$ERROR_LOG_FILE" + exec 2>"$ERROR_LOG_FILE" + fi + + # output cross-script compatibility + $verbose_mode && printf "$script_name writing compatibility file: %s\n" "$COMPATIBILITY_SCRIPT_FILE" + /bin/cat <<- COMPATIBILITY_SCRIPT > $COMPATIBILITY_SCRIPT_FILE + # exactly one of the following two global vars will be nonempty + MD_PATH=$md_path + MDQ_BASE_URL=$mdq_base_url + # output files + NO_SAML2_HTTP_ENDPOINT_FILE=${NO_SAML2_HTTP_ENDPOINT_FILE} + IDP_LOG_FILE=${IDP_LOG_FILE} + IDP_NAMES_FILE=$IDP_NAMES_FILE + ERROR_LOG_FILE=$ERROR_LOG_FILE + # temporary output directory + tmp_dir="$tmp_dir" +COMPATIBILITY_SCRIPT +} + +print_idp_names_logfile () { + $DO_NOT_PRINT_FILES && return + + local names=$1 + + printf "%s\n" "$names" >> "$IDP_NAMES_FILE" +} + +print_no_saml2_http_endpoint_logfile () { + $DO_NOT_PRINT_FILES && return + + local entityID=$1 + local registrarID=$2 + + printf "%s %s\n" "$entityID" "$registrarID" >> "$NO_SAML2_HTTP_ENDPOINT_FILE" +} + +print_logfile () { + $DO_NOT_PRINT_FILES && return + + local curl_status_code=$1 + local curl_output=$2 + local location=$3 + local binding=$4 + local entityID=$5 + local registrarID=$6 + + printf "%s %s %s " "$curl_status_code" "$curl_output" "$location" >> "$IDP_LOG_FILE" + printf "%s %s %s\n" "$binding" "$entityID" "$registrarID" >> "$IDP_LOG_FILE" +} + +##################################################################### +# Main processing +##################################################################### + +init_out_files + +if $verbose_mode; then + num_entityIDs=$( /bin/cat $IN_FILE | /usr/bin/wc -l ) + printf "$script_name processing %d entityIDs\n" $num_entityIDs +fi + +# iterate over all entityIDs in the input file +/bin/cat $IN_FILE | while read entityID; do + + # get the entity descriptor for this entityID + entityDescriptor=$( getEntityFromServer -T "$tmp_dir" -u "$MDQ_BASE_URL" $entityID ) + return_code=$? + if [ "$return_code" -ne 0 ]; then + echo "ERROR: $script_name: unable to obtain metadata for entityID: $entityID" >&2 + [ "$return_code" -gt 1 ] && exit 1 + continue + fi + + # short-circuit the while-loop if this is not an IdP + if ! echo "$entityDescriptor" | $_GREP -Fq 'IDPSSODescriptor '; then + #print_no_idp_role_logfile "$entityID" "$registrarID" + echo "WARNING: $script_name: entity is not an IdP: $entityID" >&2 + continue + fi + + # extract the registrar ID from the entity descriptor + registrarID=$( echo "$entityDescriptor" \ + | $_GREP -F -m 1 ' registrationAuthority=' \ + | $_SED -e 's/^.* registrationAuthority="\([^"]*\)".*$/\1/' + ) + + # if there is no registrar ID, work around it and continue processing + if [ -z "$registrarID" ]; then + registrarID=NULL + fi + + # compute all SSO endpoints + endpoints=$( echo "$entityDescriptor" \ + | $_GREP -E '<(md:)?SingleSignOnService ' + ) + + # iterate over the SAML2 browser-facing SSO endpoints + has_no_saml_http_endpoints=true + for binding_uri in $binding_uris; do + + # compute the SAML2 SSO endpoint + endpoint=$( echo "$endpoints" \ + | $_GREP -F -m 1 ' Binding="'$binding_uri'"' + ) + if [ -z "$endpoint" ]; then + $verbose_mode && printf "$script_name: no endpoint with Binding=\"%s\"\n" "$binding" + continue + fi + has_no_saml_http_endpoints=false + + # compute the endpoint location and binding + location=$( echo "$endpoint" \ + | $_SED -e 's/^.* Location="\([^"]*\)".*$/\1/' + ) + binding=$( echo "$endpoint" \ + | $_SED -e 's/^.* Binding="\([^"]*\)".*$/\1/' + ) + $verbose_mode && printf "$script_name probing endpoint with Location=\"%s\" and Binding=\"%s\"\n" "$location" "$binding" + + # probe the endpoint + output=$( probe_saml_idp_endpoint $curl_opts \ + -T "$tmp_dir" \ + $location $binding SingleSignOnService + ) + exit_status=$? + if [ "$exit_status" -ne 0 ]; then + echo "ERROR: $script_name: probe_saml_idp_endpoint failed ($exit_status)" >&2 + exit 3 + fi + + # parse the results + curl_error_code=$( echo "$output" | $_CUT -f1 -d" " ) + http_response_code=$( echo "$output" | $_CUT -f2 -d" " | $_SED -e 's/^.*;response:\([^;]*\).*$/\1/' ) + location=$( echo "$output" | $_CUT -f3 -d" " ) + + # interactive output + #printf "%d %d %s %s %s\n" "$curl_error_code" "$http_response_code" "$location" "$entityID" "$registrarID" + printf "%s %s %s %s\n" $( echo "$output" | $_CUT -f1-3 -d" " ) $entityID + + # file output + print_logfile $output $entityID $registrarID + + done + + if $has_no_saml_http_endpoints; then + print_no_saml2_http_endpoint_logfile "$entityID" "$registrarID" + continue + fi + + # extract the IdP names and print them to a file + names=$( echo "$entityDescriptor" \ + | /usr/bin/xsltproc $LIB_DIR/extract_IdP_names.xsl - + ) + status_code=$? + if [ "$status_code" -ne 0 ]; then + echo "ERROR: $script_name: unable to extract IdP names for entityID: $entityID" >&2 + continue + fi + print_idp_names_logfile "$names" + +done + +exit 0 diff --git a/install.sh b/install.sh index e102745..caa68d1 100755 --- a/install.sh +++ b/install.sh @@ -95,6 +95,7 @@ done <