dehydrated-0.7.2/0000755000175000017500000000000015012216410013467 5ustar lukas2511lukas2511dehydrated-0.7.2/.github/0000755000175000017500000000000015012216410015027 5ustar lukas2511lukas2511dehydrated-0.7.2/.github/FUNDING.yml0000644000175000017500000000016215012216410016643 0ustar lukas2511lukas2511github: lukas2511 custom: ["https://paypal.me/lukas2511", "http://www.amazon.de/registry/wishlist/1TUCFJK35IO4Q"] dehydrated-0.7.2/dehydrated0000755000175000017500000026445015012216410015545 0ustar lukas2511lukas2511#!/usr/bin/env bash # dehydrated by lukas2511 # Source: https://dehydrated.io # # This script is licensed under The MIT License (see LICENSE for more information). set -e set -u set -o pipefail [[ -n "${ZSH_VERSION:-}" ]] && set -o SH_WORD_SPLIT && set +o FUNCTION_ARGZERO && set -o NULL_GLOB && set -o noglob [[ -z "${ZSH_VERSION:-}" ]] && shopt -s nullglob && set -f umask 077 # paranoid umask, we're creating private keys # Close weird external file descriptors exec 3>&- exec 4>&- VERSION="0.7.2" # Find directory in which this script is stored by traversing all symbolic links SOURCE="${0}" while [ -h "$SOURCE" ]; do # resolve $SOURCE until the file is no longer a symlink DIR="$( cd -P "$( dirname "$SOURCE" )" && pwd )" SOURCE="$(readlink "$SOURCE")" [[ $SOURCE != /* ]] && SOURCE="$DIR/$SOURCE" # if $SOURCE was a relative symlink, we need to resolve it relative to the path where the symlink file was located done SCRIPTDIR="$( cd -P "$( dirname "$SOURCE" )" && pwd )" BASEDIR="${SCRIPTDIR}" ORIGARGS=("${@}") noglob_set() { if [[ -n "${ZSH_VERSION:-}" ]]; then set +o noglob else set +f fi } noglob_clear() { if [[ -n "${ZSH_VERSION:-}" ]]; then set -o noglob else set -f fi } # Generate json.sh path matching string json_path() { if [ ! "${1}" = "-p" ]; then printf '"%s"' "${1}" else printf '%s' "${2}" fi } # Get string value from json dictionary get_json_string_value() { local filter filter="$(printf 's/.*\[%s\][[:space:]]*"\([^"]*\)"/\\1/p' "$(json_path "${1:-}" "${2:-}")")" sed -n "${filter}" } # Get array values from json dictionary get_json_array_values() { grep -E '^\['"$(json_path "${1:-}" "${2:-}")"',[0-9]*\]' | sed -e 's/\[[^\]*\][[:space:]]*//g' -e 's/^"//' -e 's/"$//' } # Get sub-dictionary from json get_json_dict_value() { local filter filter="$(printf 's/.*\[%s\][[:space:]]*\(.*\)/\\1/p' "$(json_path "${1:-}" "${2:-}")")" sed -n "${filter}" | jsonsh } # Get integer value from json get_json_int_value() { local filter filter="$(printf 's/.*\[%s\][[:space:]]*\([^"]*\)/\\1/p' "$(json_path "${1:-}" "${2:-}")")" sed -n "${filter}" } # Get boolean value from json get_json_bool_value() { local filter filter="$(printf 's/.*\[%s\][[:space:]]*\([^"]*\)/\\1/p' "$(json_path "${1:-}" "${2:-}")")" sed -n "${filter}" } # JSON.sh JSON-parser # Modified from https://github.com/dominictarr/JSON.sh # Original Copyright (c) 2011 Dominic Tarr # Licensed under The MIT License jsonsh() { throw() { echo "$*" >&2 exit 1 } awk_egrep () { local pattern_string=$1 awk '{ while ($0) { start=match($0, pattern); token=substr($0, start, RLENGTH); print token; $0=substr($0, start+RLENGTH); } }' pattern="$pattern_string" } tokenize () { local GREP local ESCAPE local CHAR if echo "test string" | grep -Eao --color=never "test" >/dev/null 2>&1 then GREP='grep -Eao --color=never' else GREP='grep -Eao' fi # shellcheck disable=SC2196 if echo "test string" | grep -Eao "test" >/dev/null 2>&1 then ESCAPE='(\\[^u[:cntrl:]]|\\u[0-9a-fA-F]{4})' CHAR='[^[:cntrl:]"\\]' else GREP=awk_egrep ESCAPE='(\\\\[^u[:cntrl:]]|\\u[0-9a-fA-F]{4})' CHAR='[^[:cntrl:]"\\\\]' fi local STRING="\"$CHAR*($ESCAPE$CHAR*)*\"" local NUMBER='-?(0|[1-9][0-9]*)([.][0-9]*)?([eE][+-]?[0-9]*)?' local KEYWORD='null|false|true' local SPACE='[[:space:]]+' # Force zsh to expand $A into multiple words local is_wordsplit_disabled is_wordsplit_disabled="$(unsetopt 2>/dev/null | grep -c '^shwordsplit$' || true)" if [ "${is_wordsplit_disabled}" != "0" ]; then setopt shwordsplit; fi $GREP "$STRING|$NUMBER|$KEYWORD|$SPACE|." | grep -Ev "^$SPACE$" if [ "${is_wordsplit_disabled}" != "0" ]; then unsetopt shwordsplit; fi } parse_array () { local index=0 local ary='' read -r token case "$token" in ']') ;; *) while : do parse_value "$1" "$index" index=$((index+1)) ary="$ary""$value" read -r token case "$token" in ']') break ;; ',') ary="$ary," ;; *) throw "EXPECTED , or ] GOT ${token:-EOF}" ;; esac read -r token done ;; esac value=$(printf '[%s]' "$ary") || value= : } parse_object () { local key local obj='' read -r token case "$token" in '}') ;; *) while : do case "$token" in '"'*'"') key=$token ;; *) throw "EXPECTED string GOT ${token:-EOF}" ;; esac read -r token case "$token" in ':') ;; *) throw "EXPECTED : GOT ${token:-EOF}" ;; esac read -r token parse_value "$1" "$key" obj="$obj$key:$value" read -r token case "$token" in '}') break ;; ',') obj="$obj," ;; *) throw "EXPECTED , or } GOT ${token:-EOF}" ;; esac read -r token done ;; esac value=$(printf '{%s}' "$obj") || value= : } parse_value () { local jpath="${1:+$1,}${2:-}" case "$token" in '{') parse_object "$jpath" ;; '[') parse_array "$jpath" ;; # At this point, the only valid single-character tokens are digits. ''|[!0-9]) throw "EXPECTED value GOT ${token:-EOF}" ;; *) value="${token//\\\///}" # replace solidus ("\/") in json strings with normalized value: "/" ;; esac [ "$value" = '' ] && return [ -z "$jpath" ] && return # do not print head printf "[%s]\t%s\n" "$jpath" "$value" : } parse () { read -r token parse_value read -r token || true case "$token" in '') ;; *) throw "EXPECTED EOF GOT $token" ;; esac } tokenize | parse } # Convert IP addresses to their reverse dns variants. # Used for ALPN certs as validation for IPs uses this in SNI since IPs aren't allowed there. ip_to_ptr() { ip="$(cat)" if [[ "${ip}" =~ : ]]; then printf "%sip6.arpa" "$(printf "%s" "${ip}" | awk -F: 'BEGIN {OFS=""; }{addCount = 9 - NF; for(i=1; i<=NF;i++){if(length($i) == 0){ for(j=1;j<=addCount;j++){$i = ($i "0000");} } else { $i = substr(("0000" $i), length($i)+5-4);}}; print}' | rev | sed -e "s/./&./g")" else printf "%s.in-addr.arpa" "$(printf "%s" "${ip}" | awk -F. '{print $4"."$3"." $2"."$1}')" fi } # Create (identifiable) temporary files _mktemp() { mktemp "${TMPDIR:-/tmp}/dehydrated-XXXXXX" } # Check for script dependencies check_dependencies() { # look for required binaries for binary in grep mktemp diff sed awk curl cut head tail hexdump; do bin_path="$(command -v "${binary}" 2>/dev/null)" || _exiterr "This script requires ${binary}." [[ -x "${bin_path}" ]] || _exiterr "${binary} found in PATH but it's not executable" done # just execute some dummy and/or version commands to see if required tools are actually usable "${OPENSSL}" version > /dev/null 2>&1 || _exiterr "This script requires an openssl binary." _sed "" < /dev/null > /dev/null 2>&1 || _exiterr "This script requires sed with support for extended (modern) regular expressions." # curl returns with an error code in some ancient versions so we have to catch that set +e CURL_VERSION="$(curl -V 2>&1 | head -n1 | awk '{print $2}')" set -e } store_configvars() { __KEY_ALGO="${KEY_ALGO}" __OCSP_MUST_STAPLE="${OCSP_MUST_STAPLE}" __OCSP_FETCH="${OCSP_FETCH}" __OCSP_DAYS="${OCSP_DAYS}" __PRIVATE_KEY_RENEW="${PRIVATE_KEY_RENEW}" __PRIVATE_KEY_ROLLOVER="${PRIVATE_KEY_ROLLOVER}" __KEYSIZE="${KEYSIZE}" __CHALLENGETYPE="${CHALLENGETYPE}" __HOOK="${HOOK}" __PREFERRED_CHAIN="${PREFERRED_CHAIN}" __WELLKNOWN="${WELLKNOWN}" __HOOK_CHAIN="${HOOK_CHAIN}" __OPENSSL_CNF="${OPENSSL_CNF}" __RENEW_DAYS="${RENEW_DAYS}" __IP_VERSION="${IP_VERSION}" __ACME_PROFILE="${ACME_PROFILE}" __ORDER_TIMEOUT=${ORDER_TIMEOUT} } reset_configvars() { KEY_ALGO="${__KEY_ALGO}" OCSP_MUST_STAPLE="${__OCSP_MUST_STAPLE}" OCSP_FETCH="${__OCSP_FETCH}" OCSP_DAYS="${__OCSP_DAYS}" PRIVATE_KEY_RENEW="${__PRIVATE_KEY_RENEW}" PRIVATE_KEY_ROLLOVER="${__PRIVATE_KEY_ROLLOVER}" KEYSIZE="${__KEYSIZE}" CHALLENGETYPE="${__CHALLENGETYPE}" HOOK="${__HOOK}" PREFERRED_CHAIN="${__PREFERRED_CHAIN}" WELLKNOWN="${__WELLKNOWN}" HOOK_CHAIN="${__HOOK_CHAIN}" OPENSSL_CNF="${__OPENSSL_CNF}" RENEW_DAYS="${__RENEW_DAYS}" IP_VERSION="${__IP_VERSION}" ACME_PROFILE="${__ACME_PROFILE}" ORDER_TIMEOUT=${__ORDER_TIMEOUT} } hookscript_bricker_hook() { # Hook scripts should ignore any hooks they don't know. # Calling a random hook to make this clear to the hook script authors... if [[ -n "${HOOK}" ]]; then "${HOOK}" "this_hookscript_is_broken__dehydrated_is_working_fine__please_ignore_unknown_hooks_in_your_script" || _exiterr "Please check your hook script, it should exit cleanly without doing anything on unknown/new hooks." fi } # verify configuration values verify_config() { [[ "${CHALLENGETYPE}" == "http-01" || "${CHALLENGETYPE}" == "dns-01" || "${CHALLENGETYPE}" == "tls-alpn-01" ]] || _exiterr "Unknown challenge type ${CHALLENGETYPE}... cannot continue." if [[ "${CHALLENGETYPE}" = "dns-01" ]] && [[ -z "${HOOK}" ]]; then _exiterr "Challenge type dns-01 needs a hook script for deployment... cannot continue." fi if [[ "${CHALLENGETYPE}" = "http-01" && ! -d "${WELLKNOWN}" && ! "${COMMAND:-}" = "register" ]]; then _exiterr "WELLKNOWN directory doesn't exist, please create ${WELLKNOWN} and set appropriate permissions." fi [[ "${KEY_ALGO}" == "rsa" || "${KEY_ALGO}" == "prime256v1" || "${KEY_ALGO}" == "secp384r1" || "${KEY_ALGO}" == "secp521r1" ]] || _exiterr "Unknown public key algorithm ${KEY_ALGO}... cannot continue." if [[ -n "${IP_VERSION}" ]]; then [[ "${IP_VERSION}" = "4" || "${IP_VERSION}" = "6" ]] || _exiterr "Unknown IP version ${IP_VERSION}... cannot continue." fi [[ "${API}" == "auto" || "${API}" == "1" || "${API}" == "2" ]] || _exiterr "Unsupported API version defined in config: ${API}" [[ "${OCSP_DAYS}" =~ ^[0-9]+$ ]] || _exiterr "OCSP_DAYS must be a number" [[ "${ORDER_TIMEOUT}" =~ ^[0-9]+$ ]] || _exiterr "ORDER_TIMEOUT must be a number" } # Setup default config values, search for and load configuration files load_config() { # Check for config in various locations if [[ -z "${CONFIG:-}" ]]; then for check_config in "/etc/dehydrated" "/usr/local/etc/dehydrated" "${PWD}" "${SCRIPTDIR}"; do if [[ -f "${check_config}/config" ]]; then BASEDIR="${check_config}" CONFIG="${check_config}/config" break fi done fi # Preset CA_ZEROSSL="https://acme.zerossl.com/v2/DV90" CA_LETSENCRYPT="https://acme-v02.api.letsencrypt.org/directory" CA_LETSENCRYPT_TEST="https://acme-staging-v02.api.letsencrypt.org/directory" CA_BUYPASS="https://api.buypass.com/acme/directory" CA_BUYPASS_TEST="https://api.test4.buypass.no/acme/directory" CA_GOOGLE="https://dv.acme-v02.api.pki.goog/directory" CA_GOOGLE_TEST="https://dv.acme-v02.test-api.pki.goog/directory" # Default values CA="letsencrypt" OLDCA= CERTDIR= ALPNCERTDIR= ACCOUNTDIR= ACCOUNT_KEYSIZE="4096" ACCOUNT_KEY_ALGO=rsa CHALLENGETYPE="http-01" CONFIG_D= CURL_OPTS= DOMAINS_D= DOMAINS_TXT= HOOK= PREFERRED_CHAIN= HOOK_CHAIN="no" RENEW_DAYS="32" KEYSIZE="4096" WELLKNOWN= PRIVATE_KEY_RENEW="yes" PRIVATE_KEY_ROLLOVER="no" KEY_ALGO=secp384r1 OPENSSL=openssl OPENSSL_CNF= CONTACT_EMAIL= LOCKFILE= OCSP_MUST_STAPLE="no" OCSP_FETCH="no" OCSP_DAYS=5 IP_VERSION= CHAINCACHE= AUTO_CLEANUP="no" AUTO_CLEANUP_DELETE="no" DEHYDRATED_USER= DEHYDRATED_GROUP= API="auto" ACME_PROFILE="" ORDER_TIMEOUT=0 if [[ -z "${CONFIG:-}" ]]; then echo "#" >&2 echo "# !! WARNING !! No main config file found, using default config!" >&2 echo "#" >&2 elif [[ -f "${CONFIG}" ]]; then echo "# INFO: Using main config file ${CONFIG}" BASEDIR="$(dirname "${CONFIG}")" # shellcheck disable=SC1090 . "${CONFIG}" else _exiterr "Specified config file doesn't exist." fi if [[ -n "${CONFIG_D}" ]]; then if [[ ! -d "${CONFIG_D}" ]]; then _exiterr "The path ${CONFIG_D} specified for CONFIG_D does not point to a directory." fi # Allow globbing noglob_set for check_config_d in "${CONFIG_D}"/*.sh; do if [[ -f "${check_config_d}" ]] && [[ -r "${check_config_d}" ]]; then echo "# INFO: Using additional config file ${check_config_d}" # shellcheck disable=SC1090 . "${check_config_d}" else _exiterr "Specified additional config ${check_config_d} is not readable or not a file at all." fi done # Disable globbing noglob_clear fi # Check for missing dependencies check_dependencies has_sudo() { command -v sudo > /dev/null 2>&1 || _exiterr "DEHYDRATED_USER set but sudo not available. Please install sudo." } # Check if we are running & are allowed to run as root if [[ -n "$DEHYDRATED_USER" ]]; then command -v getent > /dev/null 2>&1 || _exiterr "DEHYDRATED_USER set but getent not available. Please install getent." TARGET_UID="$(getent passwd "${DEHYDRATED_USER}" | cut -d':' -f3)" || _exiterr "DEHYDRATED_USER ${DEHYDRATED_USER} is invalid" if [[ -z "${DEHYDRATED_GROUP}" ]]; then if [[ "${EUID}" != "${TARGET_UID}" ]]; then echo "# INFO: Running $0 as ${DEHYDRATED_USER}" has_sudo && exec sudo -u "${DEHYDRATED_USER}" "${0}" "${ORIGARGS[@]}" fi else TARGET_GID="$(getent group "${DEHYDRATED_GROUP}" | cut -d':' -f3)" || _exiterr "DEHYDRATED_GROUP ${DEHYDRATED_GROUP} is invalid" if [[ -z "${EGID:-}" ]]; then command -v id > /dev/null 2>&1 || _exiterr "DEHYDRATED_GROUP set, don't know current gid and 'id' not available... Please provide 'id' binary." EGID="$(id -g)" fi if [[ "${EUID}" != "${TARGET_UID}" ]] || [[ "${EGID}" != "${TARGET_GID}" ]]; then echo "# INFO: Running $0 as ${DEHYDRATED_USER}/${DEHYDRATED_GROUP}" has_sudo && exec sudo -u "${DEHYDRATED_USER}" -g "${DEHYDRATED_GROUP}" "${0}" "${ORIGARGS[@]}" fi fi elif [[ -n "${DEHYDRATED_GROUP}" ]]; then _exiterr "DEHYDRATED_GROUP can only be used in combination with DEHYDRATED_USER." fi # Remove slash from end of BASEDIR. Mostly for cleaner outputs, doesn't change functionality. [[ "$BASEDIR" != "/" ]] && BASEDIR="${BASEDIR%%/}" # Check BASEDIR and set default variables [[ -d "${BASEDIR}" ]] || _exiterr "BASEDIR does not exist: ${BASEDIR}" # Check for ca cli parameter if [ -n "${PARAM_CA:-}" ]; then CA="${PARAM_CA}" fi # Preset CAs if [ "${CA}" = "letsencrypt" ]; then CA="${CA_LETSENCRYPT}" elif [ "${CA}" = "letsencrypt-test" ]; then CA="${CA_LETSENCRYPT_TEST}" elif [ "${CA}" = "zerossl" ]; then CA="${CA_ZEROSSL}" elif [ "${CA}" = "buypass" ]; then CA="${CA_BUYPASS}" elif [ "${CA}" = "buypass-test" ]; then CA="${CA_BUYPASS_TEST}" elif [ "${CA}" = "google" ]; then CA="${CA_GOOGLE}" elif [ "${CA}" = "google-test" ]; then CA="${CA_GOOGLE_TEST}" fi if [[ -z "${OLDCA}" ]] && [[ "${CA}" = "https://acme-v02.api.letsencrypt.org/directory" ]]; then OLDCA="https://acme-v01.api.letsencrypt.org/directory" fi # Create new account directory or symlink to account directory from old CA # dev note: keep in mind that because of the use of 'echo' instead of 'printf' or # similar there is a newline encoded in the directory name. not going to fix this # since it's a non-issue and trying to fix existing installations would be too much # trouble CAHASH="$(echo "${CA}" | urlbase64)" [[ -z "${ACCOUNTDIR}" ]] && ACCOUNTDIR="${BASEDIR}/accounts" if [[ ! -e "${ACCOUNTDIR}/${CAHASH}" ]]; then OLDCAHASH="$(echo "${OLDCA}" | urlbase64)" mkdir -p "${ACCOUNTDIR}" if [[ -n "${OLDCA}" ]] && [[ -e "${ACCOUNTDIR}/${OLDCAHASH}" ]]; then echo "! Reusing account from ${OLDCA}" ln -s "${OLDCAHASH}" "${ACCOUNTDIR}/${CAHASH}" else mkdir "${ACCOUNTDIR}/${CAHASH}" fi fi # shellcheck disable=SC1090 [[ -f "${ACCOUNTDIR}/${CAHASH}/config" ]] && . "${ACCOUNTDIR}/${CAHASH}/config" ACCOUNT_KEY="${ACCOUNTDIR}/${CAHASH}/account_key.pem" ACCOUNT_KEY_JSON="${ACCOUNTDIR}/${CAHASH}/registration_info.json" ACCOUNT_ID_JSON="${ACCOUNTDIR}/${CAHASH}/account_id.json" ACCOUNT_DEACTIVATED="${ACCOUNTDIR}/${CAHASH}/deactivated" if [[ -f "${ACCOUNT_DEACTIVATED}" ]]; then _exiterr "Account has been deactivated. Remove account and create a new one using --register." fi if [[ -f "${BASEDIR}/private_key.pem" ]] && [[ ! -f "${ACCOUNT_KEY}" ]]; then echo "! Moving private_key.pem to ${ACCOUNT_KEY}" mv "${BASEDIR}/private_key.pem" "${ACCOUNT_KEY}" fi if [[ -f "${BASEDIR}/private_key.json" ]] && [[ ! -f "${ACCOUNT_KEY_JSON}" ]]; then echo "! Moving private_key.json to ${ACCOUNT_KEY_JSON}" mv "${BASEDIR}/private_key.json" "${ACCOUNT_KEY_JSON}" fi [[ -z "${CERTDIR}" ]] && CERTDIR="${BASEDIR}/certs" [[ -z "${ALPNCERTDIR}" ]] && ALPNCERTDIR="${BASEDIR}/alpn-certs" [[ -z "${CHAINCACHE}" ]] && CHAINCACHE="${BASEDIR}/chains" [[ -z "${DOMAINS_TXT}" ]] && DOMAINS_TXT="${BASEDIR}/domains.txt" [[ -z "${WELLKNOWN}" ]] && WELLKNOWN="/var/www/dehydrated" [[ -z "${LOCKFILE}" ]] && LOCKFILE="${BASEDIR}/lock" [[ -z "${OPENSSL_CNF}" ]] && OPENSSL_CNF="$("${OPENSSL}" version -d | cut -d\" -f2)/openssl.cnf" [[ -n "${PARAM_LOCKFILE_SUFFIX:-}" ]] && LOCKFILE="${LOCKFILE}-${PARAM_LOCKFILE_SUFFIX}" [[ -n "${PARAM_NO_LOCK:-}" ]] && LOCKFILE="" [[ -n "${PARAM_HOOK:-}" ]] && HOOK="${PARAM_HOOK}" [[ -n "${PARAM_DOMAINS_TXT:-}" ]] && DOMAINS_TXT="${PARAM_DOMAINS_TXT}" [[ -n "${PARAM_PREFERRED_CHAIN:-}" ]] && PREFERRED_CHAIN="${PARAM_PREFERRED_CHAIN}" [[ -n "${PARAM_CERTDIR:-}" ]] && CERTDIR="${PARAM_CERTDIR}" [[ -n "${PARAM_ALPNCERTDIR:-}" ]] && ALPNCERTDIR="${PARAM_ALPNCERTDIR}" [[ -n "${PARAM_CHALLENGETYPE:-}" ]] && CHALLENGETYPE="${PARAM_CHALLENGETYPE}" [[ -n "${PARAM_KEY_ALGO:-}" ]] && KEY_ALGO="${PARAM_KEY_ALGO}" [[ -n "${PARAM_OCSP_MUST_STAPLE:-}" ]] && OCSP_MUST_STAPLE="${PARAM_OCSP_MUST_STAPLE}" [[ -n "${PARAM_IP_VERSION:-}" ]] && IP_VERSION="${PARAM_IP_VERSION}" [[ -n "${PARAM_ACME_PROFILE:-}" ]] && ACME_PROFILE="${PARAM_ACME_PROFILE}" [[ -n "${PARAM_ORDER_TIMEOUT:-}" ]] && ORDER_TIMEOUT="${PARAM_ORDER_TIMEOUT}" if [ "${PARAM_FORCE_VALIDATION:-no}" = "yes" ] && [ "${PARAM_FORCE:-no}" = "no" ]; then _exiterr "Argument --force-validation can only be used in combination with --force (-x)" fi if [ ! "${1:-}" = "noverify" ]; then verify_config fi store_configvars } # Initialize system init_system() { load_config # Lockfile handling (prevents concurrent access) if [[ -n "${LOCKFILE}" ]]; then LOCKDIR="$(dirname "${LOCKFILE}")" [[ -w "${LOCKDIR}" ]] || _exiterr "Directory ${LOCKDIR} for LOCKFILE ${LOCKFILE} is not writable, aborting." ( set -C; date > "${LOCKFILE}" ) 2>/dev/null || _exiterr "Lock file '${LOCKFILE}' present, aborting." remove_lock() { rm -f "${LOCKFILE}"; } trap 'remove_lock' EXIT fi # Get CA URLs CA_DIRECTORY="$(http_request get "${CA}" | jsonsh)" # Automatic discovery of API version if [[ "${API}" = "auto" ]]; then grep -q newOrder <<< "${CA_DIRECTORY}" && API=2 || API=1 fi # shellcheck disable=SC2015 if [[ "${API}" = "1" ]]; then CA_NEW_CERT="$(printf "%s" "${CA_DIRECTORY}" | get_json_string_value new-cert)" && CA_NEW_AUTHZ="$(printf "%s" "${CA_DIRECTORY}" | get_json_string_value new-authz)" && CA_NEW_REG="$(printf "%s" "${CA_DIRECTORY}" | get_json_string_value new-reg)" && CA_TERMS="$(printf "%s" "${CA_DIRECTORY}" | get_json_string_value terms-of-service)" && CA_REQUIRES_EAB="false" && CA_REVOKE_CERT="$(printf "%s" "${CA_DIRECTORY}" | get_json_string_value revoke-cert)" || _exiterr "Problem retrieving ACME/CA-URLs, check if your configured CA points to the directory entrypoint." # Since reg URI is missing from directory we will assume it is the same as CA_NEW_REG without the new part CA_REG=${CA_NEW_REG/new-reg/reg} if [[ -n "${ACME_PROFILE}" ]]; then _exiterr "ACME profiles are not supported in ACME v1." fi else CA_NEW_ORDER="$(printf "%s" "${CA_DIRECTORY}" | get_json_string_value newOrder)" && CA_NEW_NONCE="$(printf "%s" "${CA_DIRECTORY}" | get_json_string_value newNonce)" && CA_NEW_ACCOUNT="$(printf "%s" "${CA_DIRECTORY}" | get_json_string_value newAccount)" && CA_TERMS="$(printf "%s" "${CA_DIRECTORY}" | get_json_string_value -p '"meta","termsOfService"')" && CA_REQUIRES_EAB="$(printf "%s" "${CA_DIRECTORY}" | get_json_bool_value -p '"meta","externalAccountRequired"' || echo false)" && CA_REVOKE_CERT="$(printf "%s" "${CA_DIRECTORY}" | get_json_string_value revokeCert)" || _exiterr "Problem retrieving ACME/CA-URLs, check if your configured CA points to the directory entrypoint." # Checking ACME profile if [[ -n "${ACME_PROFILE}" ]]; then # Extract available profiles from CA directory declare -A available_profiles=() while IFS=$'\t' read -r path value; do if [[ "${value}" =~ ^\"([^\"]+)\"$ ]]; then value=${BASH_REMATCH[1]} fi if [[ "${path}" =~ ^\[\"([^\"]+)\"\]$ ]]; then available_profiles[${BASH_REMATCH[1]}]=$value fi done <<< "$(printf "%s" "${CA_DIRECTORY}" | get_json_dict_value -p '"meta","profiles"' 2>/dev/null)" if [[ ${#available_profiles[@]} -eq 0 ]]; then _exiterr "ACME profile not supported by this CA" fi # Check if the requested profile is available found_profile="no" for profile in "${!available_profiles[@]}"; do if [[ "${profile}" == "${ACME_PROFILE}" ]]; then found_profile="yes" break fi done if [[ "${found_profile}" == "no" ]]; then _exiterr "ACME profile '${ACME_PROFILE}' not found, available profiles:$(for key in "${!available_profiles[@]}"; do printf "\n %s: %s" "${key}" "${available_profiles[$key]}"; done)" fi fi fi # Export some environment variables to be used in hook script export WELLKNOWN BASEDIR CERTDIR ALPNCERTDIR CONFIG COMMAND # Checking for private key ... register_new_key="no" generated="false" if [[ -n "${PARAM_ACCOUNT_KEY:-}" ]]; then # a private key was specified from the command line so use it for this run echo "Using private key ${PARAM_ACCOUNT_KEY} instead of account key" ACCOUNT_KEY="${PARAM_ACCOUNT_KEY}" ACCOUNT_KEY_JSON="${PARAM_ACCOUNT_KEY}.json" ACCOUNT_ID_JSON="${PARAM_ACCOUNT_KEY}_id.json" [ "${COMMAND:-}" = "register" ] && register_new_key="yes" else # Check if private account key exists, if it doesn't exist yet generate a new one (rsa key) if [[ ! -e "${ACCOUNT_KEY}" ]]; then if [[ ! "${PARAM_ACCEPT_TERMS:-}" = "yes" ]]; then printf '\n' >&2 printf 'To use dehydrated with this certificate authority you have to agree to their terms of service which you can find here: %s\n\n' "${CA_TERMS}" >&2 printf 'To accept these terms of service run "%s --register --accept-terms".\n' "${0}" >&2 exit 1 fi echo "+ Generating account key..." generated="true" local tmp_account_key tmp_account_key="$(_mktemp)" if [[ ${API} -eq 1 && ! "${ACCOUNT_KEY_ALGO}" = "rsa" ]]; then _exiterr "ACME API version 1 does not support EC account keys" fi case "${ACCOUNT_KEY_ALGO}" in rsa) _openssl genrsa -out "${tmp_account_key}" "${ACCOUNT_KEYSIZE}";; prime256v1|secp384r1|secp521r1) _openssl ecparam -genkey -name "${ACCOUNT_KEY_ALGO}" -out "${tmp_account_key}" -noout;; esac cat "${tmp_account_key}" > "${ACCOUNT_KEY}" rm "${tmp_account_key}" register_new_key="yes" fi fi if ("${OPENSSL}" rsa -in "${ACCOUNT_KEY}" -check 2>/dev/null > /dev/null); then # Get public components from private key and calculate thumbprint pubExponent64="$(printf '%x' "$("${OPENSSL}" rsa -in "${ACCOUNT_KEY}" -noout -text | awk '/publicExponent/ {print $2}')" | hex2bin | urlbase64)" pubMod64="$("${OPENSSL}" rsa -in "${ACCOUNT_KEY}" -noout -modulus | cut -d'=' -f2 | hex2bin | urlbase64)" account_key_info="$(printf '{"e":"%s","kty":"RSA","n":"%s"}' "${pubExponent64}" "${pubMod64}")" account_key_sigalgo=RS256 elif ("${OPENSSL}" ec -in "${ACCOUNT_KEY}" -check 2>/dev/null > /dev/null); then curve="$("${OPENSSL}" ec -in "${ACCOUNT_KEY}" -noout -text 2>/dev/null | grep 'NIST CURVE' | cut -d':' -f2 | tr -d ' ')" pubkey="$("${OPENSSL}" ec -in "${ACCOUNT_KEY}" -noout -text 2>/dev/null | tr -d '\n ' | grep -Eo 'pub:.*ASN1' | _sed -e 's/^pub://' -e 's/ASN1$//' | tr -d ':')" if [ "${curve}" = "P-256" ]; then account_key_sigalgo="ES256" elif [ "${curve}" = "P-384" ]; then account_key_sigalgo="ES384" elif [ "${curve}" = "P-521" ]; then account_key_sigalgo="ES512" else _exiterr "Unknown account key curve: ${curve}" fi ec_x_offset=2 ec_x_len=$((${#pubkey}/2 - 1)) ec_x="${pubkey:$ec_x_offset:$ec_x_len}" ec_x64="$(printf "%s" "${ec_x}" | hex2bin | urlbase64)" ec_y_offset=$((ec_x_offset+ec_x_len)) ec_y_len=$((${#pubkey}-ec_y_offset)) ec_y="${pubkey:$ec_y_offset:$ec_y_len}" ec_y64="$(printf "%s" "${ec_y}" | hex2bin | urlbase64)" account_key_info="$(printf '{"crv":"%s","kty":"EC","x":"%s","y":"%s"}' "${curve}" "${ec_x64}" "${ec_y64}")" else _exiterr "Account key is not valid, cannot continue." fi thumbprint="$(printf '%s' "${account_key_info}" | "${OPENSSL}" dgst -sha256 -binary | urlbase64)" # If we generated a new private key in the step above we have to register it with the acme-server if [[ "${register_new_key}" = "yes" ]]; then echo "+ Registering account key with ACME server..." FAILED=false if [[ ${API} -eq 1 && -z "${CA_NEW_REG}" ]] || [[ ${API} -eq 2 && -z "${CA_NEW_ACCOUNT}" ]]; then echo "Certificate authority doesn't allow registrations." FAILED=true fi # ZeroSSL special sauce if [[ "${CA}" = "${CA_ZEROSSL}" ]]; then if [[ -z "${EAB_KID:-}" ]] || [[ -z "${EAB_HMAC_KEY:-}" ]]; then if [[ -z "${CONTACT_EMAIL}" ]]; then echo "ZeroSSL requires contact email to be set or EAB_KID/EAB_HMAC_KEY to be manually configured" FAILED=true else zeroapi="$(curl -s "https://api.zerossl.com/acme/eab-credentials-email" -d "email=${CONTACT_EMAIL}" | jsonsh)" EAB_KID="$(printf "%s" "${zeroapi}" | get_json_string_value eab_kid)" EAB_HMAC_KEY="$(printf "%s" "${zeroapi}" | get_json_string_value eab_hmac_key)" if [[ -z "${EAB_KID:-}" ]] || [[ -z "${EAB_HMAC_KEY:-}" ]]; then echo "Unknown error retrieving ZeroSSL API credentials" echo "${zeroapi}" FAILED=true fi fi fi fi # Google special sauce if [[ "${CA}" = "${CA_GOOGLE}" ]]; then if [[ -z "${CONTACT_EMAIL}" ]] || [[ -z "${EAB_KID:-}" ]] || [[ -z "${EAB_HMAC_KEY:-}" ]]; then echo "Google requires contact email, EAB_KID and EAB_HMAC_KEY to be manually configured (see https://cloud.google.com/certificate-manager/docs/public-ca-tutorial)" FAILED=true fi fi # Check if external account is required if [[ "${FAILED}" = "false" ]]; then if [[ "${CA_REQUIRES_EAB}" = "true" ]]; then if [[ -z "${EAB_KID:-}" ]] || [[ -z "${EAB_HMAC_KEY:-}" ]]; then FAILED=true echo "This CA requires an external account but no EAB_KID/EAB_HMAC_KEY has been configured" fi fi fi # If an email for the contact has been provided then adding it to the registration request if [[ "${FAILED}" = "false" ]]; then if [[ ${API} -eq 1 ]]; then if [[ -n "${CONTACT_EMAIL}" ]]; then (signed_request "${CA_NEW_REG}" '{"resource": "new-reg", "contact":["mailto:'"${CONTACT_EMAIL}"'"], "agreement": "'"${CA_TERMS}"'"}' > "${ACCOUNT_KEY_JSON}") || FAILED=true else (signed_request "${CA_NEW_REG}" '{"resource": "new-reg", "agreement": "'"${CA_TERMS}"'"}' > "${ACCOUNT_KEY_JSON}") || FAILED=true fi else if [[ -n "${EAB_KID:-}" ]] && [[ -n "${EAB_HMAC_KEY:-}" ]]; then eab_url="${CA_NEW_ACCOUNT}" eab_protected64="$(printf '{"alg":"HS256","kid":"%s","url":"%s"}' "${EAB_KID}" "${eab_url}" | urlbase64)" eab_payload64="$(printf "%s" "${account_key_info}" | urlbase64)" eab_key="$(printf "%s" "${EAB_HMAC_KEY}" | deurlbase64 | bin2hex)" eab_signed64="$(printf '%s' "${eab_protected64}.${eab_payload64}" | "${OPENSSL}" dgst -binary -sha256 -mac HMAC -macopt "hexkey:${eab_key}" | urlbase64)" if [[ -n "${CONTACT_EMAIL}" ]]; then regjson='{"contact":["mailto:'"${CONTACT_EMAIL}"'"], "termsOfServiceAgreed": true, "externalAccountBinding": {"protected": "'"${eab_protected64}"'", "payload": "'"${eab_payload64}"'", "signature": "'"${eab_signed64}"'"}}' else regjson='{"termsOfServiceAgreed": true, "externalAccountBinding": {"protected": "'"${eab_protected64}"'", "payload": "'"${eab_payload64}"'", "signature": "'"${eab_signed64}"'"}}' fi else if [[ -n "${CONTACT_EMAIL}" ]]; then regjson='{"contact":["mailto:'"${CONTACT_EMAIL}"'"], "termsOfServiceAgreed": true}' else regjson='{"termsOfServiceAgreed": true}' fi fi (signed_request "${CA_NEW_ACCOUNT}" "${regjson}" > "${ACCOUNT_KEY_JSON}") || FAILED=true fi fi if [[ "${FAILED}" = "true" ]]; then echo >&2 echo >&2 echo "Error registering account key. See message above for more information." >&2 if [[ "${generated}" = "true" ]]; then rm "${ACCOUNT_KEY}" fi rm -f "${ACCOUNT_KEY_JSON}" exit 1 fi elif [[ "${COMMAND:-}" = "register" ]]; then echo "+ Account already registered!" exit 0 fi # Read account information or request from CA if missing if [[ -e "${ACCOUNT_KEY_JSON}" ]]; then if [[ ${API} -eq 1 ]]; then ACCOUNT_ID="$(jsonsh < "${ACCOUNT_KEY_JSON}" | get_json_int_value id)" ACCOUNT_URL="${CA_REG}/${ACCOUNT_ID}" else if [[ -e "${ACCOUNT_ID_JSON}" ]]; then ACCOUNT_URL="$(jsonsh < "${ACCOUNT_ID_JSON}" | get_json_string_value url)" fi # if account URL is not storred, fetch it from the CA if [[ -z "${ACCOUNT_URL:-}" ]]; then echo "+ Fetching account URL..." ACCOUNT_URL="$(signed_request "${CA_NEW_ACCOUNT}" '{"onlyReturnExisting": true}' 4>&1 | grep -i ^Location: | cut -d':' -f2- | tr -d ' \t\r\n')" if [[ -z "${ACCOUNT_URL}" ]]; then _exiterr "Unknown error on fetching account information" fi echo '{"url":"'"${ACCOUNT_URL}"'"}' > "${ACCOUNT_ID_JSON}" # store the URL for next time fi fi else echo "Fetching missing account information from CA..." if [[ ${API} -eq 1 ]]; then _exiterr "This is not implemented for ACMEv1! Consider switching to ACMEv2 :)" else ACCOUNT_URL="$(signed_request "${CA_NEW_ACCOUNT}" '{"onlyReturnExisting": true}' 4>&1 | grep -i ^Location: | cut -d':' -f2- | tr -d ' \t\r\n')" ACCOUNT_INFO="$(signed_request "${ACCOUNT_URL}" '{}')" fi echo "${ACCOUNT_INFO}" > "${ACCOUNT_KEY_JSON}" fi } # Different sed version for different os types... _sed() { if [[ "${OSTYPE}" = "Linux" || "${OSTYPE:0:5}" = "MINGW" ]]; then sed -r "${@}" else sed -E "${@}" fi } # Print error message and exit with error _exiterr() { if [ -n "${1:-}" ]; then echo "ERROR: ${1}" >&2 fi [[ "${skip_exit_hook:-no}" = "no" ]] && [[ -n "${HOOK:-}" ]] && ("${HOOK}" "exit_hook" "${1:-}" || echo 'exit_hook returned with non-zero exit code!' >&2) exit 1 } # Remove newlines and whitespace from json clean_json() { tr -d '\r\n' | _sed -e 's/ +/ /g' -e 's/\{ /{/g' -e 's/ \}/}/g' -e 's/\[ /[/g' -e 's/ \]/]/g' } # Encode data as url-safe formatted base64 urlbase64() { # urlbase64: base64 encoded string with '+' replaced with '-' and '/' replaced with '_' "${OPENSSL}" base64 -e | tr -d '\n\r' | _sed -e 's:=*$::g' -e 'y:+/:-_:' } # Decode data from url-safe formatted base64 deurlbase64() { data="$(cat | tr -d ' \n\r')" modlen=$((${#data} % 4)) padding="" if [[ "${modlen}" = "2" ]]; then padding="=="; elif [[ "${modlen}" = "3" ]]; then padding="="; fi printf "%s%s" "${data}" "${padding}" | tr -d '\n\r' | _sed -e 'y:-_:+/:' | "${OPENSSL}" base64 -d -A } # Convert hex string to binary data hex2bin() { # Remove spaces, add leading zero, escape as hex string and parse with printf # shellcheck disable=SC2059 printf "%b" "$(cat | _sed -e 's/[[:space:]]//g' -e 's/^(.(.{2})*)$/0\1/' -e 's/(.{2})/\\x\1/g')" } # Convert binary data to hex string bin2hex() { hexdump -v -e '/1 "%02x"' } # OpenSSL writes to stderr/stdout even when there are no errors. So just # display the output if the exit code was != 0 to simplify debugging. _openssl() { set +e out="$("${OPENSSL}" "${@}" 2>&1)" res=$? set -e if [[ ${res} -ne 0 ]]; then echo " + ERROR: failed to run $* (Exitcode: ${res})" >&2 echo >&2 echo "Details:" >&2 echo "${out}" >&2 echo >&2 exit "${res}" fi } # Send http(s) request with specified method http_request() { tempcont="$(_mktemp)" tempheaders="$(_mktemp)" if [[ -n "${IP_VERSION:-}" ]]; then ip_version="-${IP_VERSION}" fi set +e # shellcheck disable=SC2086 if [[ "${1}" = "head" ]]; then statuscode="$(curl ${ip_version:-} ${CURL_OPTS} -A "dehydrated/${VERSION} curl/${CURL_VERSION}" -s -w "%{http_code}" -o "${tempcont}" "${2}" -I)" curlret="${?}" touch "${tempheaders}" elif [[ "${1}" = "get" ]]; then statuscode="$(curl ${ip_version:-} ${CURL_OPTS} -A "dehydrated/${VERSION} curl/${CURL_VERSION}" -L -s -w "%{http_code}" -o "${tempcont}" -D "${tempheaders}" "${2}")" curlret="${?}" elif [[ "${1}" = "post" ]]; then statuscode="$(curl ${ip_version:-} ${CURL_OPTS} -A "dehydrated/${VERSION} curl/${CURL_VERSION}" -s -w "%{http_code}" -o "${tempcont}" "${2}" -D "${tempheaders}" -H 'Content-Type: application/jose+json' -d "${3}")" curlret="${?}" else set -e _exiterr "Unknown request method: ${1}" fi set -e if [[ ! "${curlret}" = "0" ]]; then _exiterr "Problem connecting to server (${1} for ${2}; curl returned with ${curlret})" fi if [[ ! "${statuscode:0:1}" = "2" ]]; then # check for existing registration warning if [[ "${API}" = "1" ]] && [[ -n "${CA_NEW_REG:-}" ]] && [[ "${2}" = "${CA_NEW_REG:-}" ]] && [[ "${statuscode}" = "409" ]] && grep -q "Registration key is already in use" "${tempcont}"; then # do nothing : # check for already-revoked warning elif [[ -n "${CA_REVOKE_CERT:-}" ]] && [[ "${2}" = "${CA_REVOKE_CERT:-}" ]] && [[ "${statuscode}" = "409" ]]; then grep -q "Certificate already revoked" "${tempcont}" && return else if grep -q "urn:ietf:params:acme:error:badNonce" "${tempcont}"; then printf "badnonce %s" "$(grep -Eoi "^replay-nonce:.*$" "${tempheaders}" | sed 's/ //' | cut -d: -f2)" return 0 fi echo " + ERROR: An error occurred while sending ${1}-request to ${2} (Status ${statuscode})" >&2 echo >&2 echo "Details:" >&2 cat "${tempheaders}" >&2 cat "${tempcont}" >&2 echo >&2 echo >&2 # An exclusive hook for the {1}-request error might be useful (e.g., for sending an e-mail to admins) if [[ -n "${HOOK}" ]]; then errtxt="$(cat "${tempcont}")" errheaders="$(cat "${tempheaders}")" "${HOOK}" "request_failure" "${statuscode}" "${errtxt}" "${1}" "${errheaders}" || _exiterr 'request_failure hook returned with non-zero exit code' fi rm -f "${tempcont}" rm -f "${tempheaders}" # remove temporary domains.txt file if used [[ "${COMMAND:-}" = "sign_domains" && -n "${PARAM_DOMAIN:-}" && -n "${DOMAINS_TXT:-}" ]] && rm "${DOMAINS_TXT}" _exiterr fi fi if { true >&4; } 2>/dev/null; then cat "${tempheaders}" >&4 fi cat "${tempcont}" rm -f "${tempcont}" rm -f "${tempheaders}" } # Send signed request signed_request() { # Encode payload as urlbase64 payload64="$(printf '%s' "${2}" | urlbase64)" if [ -n "${3:-}" ]; then nonce="$(printf "%s" "${3}" | tr -d ' \t\n\r')" else # Retrieve nonce from acme-server if [[ ${API} -eq 1 ]]; then nonce="$(http_request head "${CA}" | grep -i ^Replay-Nonce: | cut -d':' -f2- | tr -d ' \t\n\r')" else nonce="$(http_request head "${CA_NEW_NONCE}" | grep -i ^Replay-Nonce: | cut -d':' -f2- | tr -d ' \t\n\r')" fi fi if [[ ${API} -eq 1 ]]; then # Build another header which also contains the previously received nonce and encode it as urlbase64 protected='{"alg": "RS256", "jwk": {"e": "'"${pubExponent64}"'", "kty": "RSA", "n": "'"${pubMod64}"'"}, "nonce": "'"${nonce}"'"}' protected64="$(printf '%s' "${protected}" | urlbase64)" else # Build another header which also contains the previously received nonce and url and encode it as urlbase64 if [[ -n "${ACCOUNT_URL:-}" ]]; then protected='{"alg": "'"${account_key_sigalgo}"'", "kid": "'"${ACCOUNT_URL}"'", "url": "'"${1}"'", "nonce": "'"${nonce}"'"}' else protected='{"alg": "'"${account_key_sigalgo}"'", "jwk": '"${account_key_info}"', "url": "'"${1}"'", "nonce": "'"${nonce}"'"}' fi protected64="$(printf '%s' "${protected}" | urlbase64)" fi # Sign header with nonce and our payload with our private key and encode signature as urlbase64 if [[ "${account_key_sigalgo}" = "RS256" ]]; then signed64="$(printf '%s' "${protected64}.${payload64}" | "${OPENSSL}" dgst -sha256 -sign "${ACCOUNT_KEY}" | urlbase64)" else dgstparams="$(printf '%s' "${protected64}.${payload64}" | "${OPENSSL}" dgst -sha${account_key_sigalgo:2} -sign "${ACCOUNT_KEY}" | "${OPENSSL}" asn1parse -inform DER)" dgst_parm_1="$(echo "$dgstparams" | head -n 2 | tail -n 1 | cut -d':' -f4)" dgst_parm_2="$(echo "$dgstparams" | head -n 3 | tail -n 1 | cut -d':' -f4)" # zero-padding (doesn't seem to be necessary, but other clients are doing this as well... case "${account_key_sigalgo}" in "ES256") siglen=64;; "ES384") siglen=96;; "ES512") siglen=132;; esac while [[ ${#dgst_parm_1} -lt $siglen ]]; do dgst_parm_1="0${dgst_parm_1}"; done while [[ ${#dgst_parm_2} -lt $siglen ]]; do dgst_parm_2="0${dgst_parm_2}"; done signed64="$(printf "%s%s" "${dgst_parm_1}" "${dgst_parm_2}" | hex2bin | urlbase64)" fi if [[ ${API} -eq 1 ]]; then # Build header with just our public key and algorithm information header='{"alg": "RS256", "jwk": {"e": "'"${pubExponent64}"'", "kty": "RSA", "n": "'"${pubMod64}"'"}}' # Send header + extended header + payload + signature to the acme-server data='{"header": '"${header}"', "protected": "'"${protected64}"'", "payload": "'"${payload64}"'", "signature": "'"${signed64}"'"}' else # Send extended header + payload + signature to the acme-server data='{"protected": "'"${protected64}"'", "payload": "'"${payload64}"'", "signature": "'"${signed64}"'"}' fi output="$(http_request post "${1}" "${data}")" if grep -qE "^badnonce " <<< "${output}"; then echo " ! Request failed (badNonce), retrying request..." >&2 signed_request "${1:-}" "${2:-}" "$(printf "%s" "${output}" | cut -d' ' -f2)" else printf "%s" "${output}" fi } # Extracts all subject names from a CSR # Outputs either the CN, or the SANs, one per line extract_altnames() { csrfile="${1}" # path to CSR file if ! "${OPENSSL}" req -in "${csrfile}" -verify -noout >/dev/null; then _exiterr "Certificate signing request isn't valid" fi reqtext="$("${OPENSSL}" req -in "${csrfile}" -noout -text)" if <<<"${reqtext}" grep -q '^[[:space:]]*X509v3 Subject Alternative Name:[[:space:]]*$'; then # SANs used, extract these altnames="$( <<<"${reqtext}" awk '/X509v3 Subject Alternative Name:/{print;getline;print;}' | tail -n1 )" # split to one per line: # shellcheck disable=SC1003 altnames="$( <<<"${altnames}" _sed -e 's/^[[:space:]]*//; s/, /\'$'\n''/g' )" # we can only get DNS/IP: ones signed if grep -qEv '^(DNS|IP( Address)*|othername):' <<<"${altnames}"; then _exiterr "Certificate signing request contains non-DNS/IP Subject Alternative Names" fi # strip away the DNS/IP: prefix altnames="$( <<<"${altnames}" _sed -e 's/^(DNS:|IP( Address)*:|othername:)//' )" printf "%s" "${altnames}" | tr '\n' ' ' else # No SANs, extract CN altnames="$( <<<"${reqtext}" grep '^[[:space:]]*Subject:' | _sed -e 's/.*[ /]CN ?= ?([^ /,]*).*/\1/' )" printf "%s" "${altnames}" fi } # Get last issuer CN in certificate chain get_last_cn() { <<<"${1}" _sed 'H;/-----BEGIN CERTIFICATE-----/h;$!d;x' | "${OPENSSL}" x509 -noout -issuer | head -n1 | _sed -e 's/.*[ /]CN ?= ?([^/,]*).*/\1/' } # Create certificate for domain(s) and outputs it FD 3 sign_csr() { csrfile="${1}" # path to CSR file if { true >&3; } 2>/dev/null; then : # fd 3 looks OK else _exiterr "sign_csr: FD 3 not open" fi shift 1 || true export altnames="${*}" if [[ ${API} -eq 1 ]]; then if [[ -z "${CA_NEW_AUTHZ}" ]] || [[ -z "${CA_NEW_CERT}" ]]; then _exiterr "Certificate authority doesn't allow certificate signing" fi elif [[ ${API} -eq 2 ]] && [[ -z "${CA_NEW_ORDER}" ]]; then _exiterr "Certificate authority doesn't allow certificate signing" fi if [[ -n "${ZSH_VERSION:-}" ]]; then local -A challenge_names challenge_uris challenge_tokens authorizations keyauths deploy_args else local -a challenge_names challenge_uris challenge_tokens authorizations keyauths deploy_args fi # Initial step: Find which authorizations we're dealing with if [[ ${API} -eq 2 ]]; then # Request new order and store authorization URIs local challenge_identifiers="" for altname in ${altnames}; do if [[ "${altname}" =~ ^ip: ]]; then challenge_identifiers+="$(printf '{"type": "ip", "value": "%s"}, ' "${altname:3}")" else challenge_identifiers+="$(printf '{"type": "dns", "value": "%s"}, ' "${altname}")" fi done challenge_identifiers="[${challenge_identifiers%, }]" echo " + Requesting new certificate order from CA..." local order_payload='{"identifiers": '"${challenge_identifiers}" if [[ -n "${ACME_PROFILE}" ]]; then order_payload="${order_payload}"',"profile":"'"${ACME_PROFILE}"'"' fi order_payload="${order_payload}"'}' order_location="$(signed_request "${CA_NEW_ORDER}" "${order_payload}" 4>&1 | grep -i ^Location: | cut -d':' -f2- | tr -d ' \t\r\n')" result="$(signed_request "${order_location}" "" | jsonsh)" order_authorizations="$(echo "${result}" | get_json_array_values authorizations)" finalize="$(echo "${result}" | get_json_string_value finalize)" local idx=0 for uri in ${order_authorizations}; do authorizations[${idx}]="${uri}" idx=$((idx+1)) done echo " + Received ${idx} authorizations URLs from the CA" else # Copy $altnames to $authorizations (just doing this to reduce duplicate code later on) local idx=0 for altname in ${altnames}; do authorizations[${idx}]="${altname}" idx=$((idx+1)) done fi # Check if authorizations are valid and gather challenge information for pending authorizations local idx=0 for authorization in ${authorizations[*]}; do if [[ "${API}" -eq 2 ]]; then # Receive authorization ($authorization is authz uri) response="$(signed_request "$(echo "${authorization}" | _sed -e 's/\"(.*)".*/\1/')" "" | jsonsh)" identifier="$(echo "${response}" | get_json_string_value -p '"identifier","value"')" identifier_type="$(echo "${response}" | get_json_string_value -p '"identifier","type"')" echo " + Handling authorization for ${identifier}" else # Request new authorization ($authorization is altname) identifier="${authorization}" echo " + Requesting authorization for ${identifier}..." response="$(signed_request "${CA_NEW_AUTHZ}" '{"resource": "new-authz", "identifier": {"type": "dns", "value": "'"${identifier}"'"}}' | jsonsh)" fi # Check if authorization has already been validated if [ "$(echo "${response}" | get_json_string_value status)" = "valid" ]; then if [ "${PARAM_FORCE_VALIDATION:-no}" = "yes" ]; then echo " + A valid authorization has been found but will be ignored" else echo " + Found valid authorization for ${identifier}" continue fi fi # Find challenge in authorization challengeindex="$(echo "${response}" | grep -E '^\["challenges",[0-9]+,"type"\][[:space:]]+"'"${CHALLENGETYPE}"'"' | cut -d',' -f2 || true)" if [ -z "${challengeindex}" ]; then allowed_validations="$(echo "${response}" | grep -E '^\["challenges",[0-9]+,"type"\]' | sed -e 's/\[[^\]*\][[:space:]]*//g' -e 's/^"//' -e 's/"$//' | tr '\n' ' ')" _exiterr "Validating this certificate is not possible using ${CHALLENGETYPE}. Possible validation methods are: ${allowed_validations}" fi challenge="$(echo "${response}" | get_json_dict_value -p '"challenges",'"${challengeindex}")" # Gather challenge information if [ "${identifier_type:-}" = "ip" ] && [ "${CHALLENGETYPE}" = "tls-alpn-01" ] ; then challenge_names[${idx}]="$(echo "${identifier}" | ip_to_ptr)" else challenge_names[${idx}]="${identifier}" fi challenge_tokens[${idx}]="$(echo "${challenge}" | get_json_string_value token)" if [[ ${API} -eq 2 ]]; then challenge_uris[${idx}]="$(echo "${challenge}" | get_json_string_value url)" else if [[ "$(echo "${challenge}" | get_json_string_value type)" = "urn:acme:error:unauthorized" ]]; then _exiterr "Challenge unauthorized: $(echo "${challenge}" | get_json_string_value detail)" fi challenge_uris[${idx}]="$(echo "${challenge}" | get_json_dict_value validationRecord | get_json_string_value uri)" fi # Prepare challenge tokens and deployment parameters keyauth="${challenge_tokens[${idx}]}.${thumbprint}" case "${CHALLENGETYPE}" in "http-01") # Store challenge response in well-known location and make world-readable (so that a webserver can access it) printf '%s' "${keyauth}" > "${WELLKNOWN}/${challenge_tokens[${idx}]}" chmod a+r "${WELLKNOWN}/${challenge_tokens[${idx}]}" keyauth_hook="${keyauth}" ;; "dns-01") # Generate DNS entry content for dns-01 validation keyauth_hook="$(printf '%s' "${keyauth}" | "${OPENSSL}" dgst -sha256 -binary | urlbase64)" ;; "tls-alpn-01") keyauth_hook="$(printf '%s' "${keyauth}" | "${OPENSSL}" dgst -sha256 -c -hex | awk '{print $NF}')" generate_alpn_certificate "${identifier}" "${identifier_type}" "${keyauth_hook}" ;; esac keyauths[${idx}]="${keyauth}" if [ "${identifier_type:-}" = "ip" ] && [ "${CHALLENGETYPE}" = "tls-alpn-01" ]; then deploy_args[${idx}]="$(echo "${identifier}" | ip_to_ptr) ${challenge_tokens[${idx}]} ${keyauth_hook}" else deploy_args[${idx}]="${identifier} ${challenge_tokens[${idx}]} ${keyauth_hook}" fi idx=$((idx+1)) done local num_pending_challenges=${idx} echo " + ${num_pending_challenges} pending challenge(s)" # Deploy challenge tokens if [[ ${num_pending_challenges} -ne 0 ]]; then echo " + Deploying challenge tokens..." if [[ -n "${HOOK}" ]] && [[ "${HOOK_CHAIN}" = "yes" ]]; then # shellcheck disable=SC2068 "${HOOK}" "deploy_challenge" ${deploy_args[@]} || _exiterr 'deploy_challenge hook returned with non-zero exit code' elif [[ -n "${HOOK}" ]]; then # Run hook script to deploy the challenge token local idx=0 while [ ${idx} -lt ${num_pending_challenges} ]; do # shellcheck disable=SC2086 "${HOOK}" "deploy_challenge" ${deploy_args[${idx}]} || _exiterr 'deploy_challenge hook returned with non-zero exit code' idx=$((idx+1)) done fi fi # Validate pending challenges local idx=0 while [ ${idx} -lt ${num_pending_challenges} ]; do echo " + Responding to challenge for ${challenge_names[${idx}]} authorization..." # Ask the acme-server to verify our challenge and wait until it is no longer pending if [[ ${API} -eq 1 ]]; then result="$(signed_request "${challenge_uris[${idx}]}" '{"resource": "challenge", "keyAuthorization": "'"${keyauths[${idx}]}"'"}' | jsonsh)" else result="$(signed_request "${challenge_uris[${idx}]}" '{}' | jsonsh)" fi reqstatus="$(echo "${result}" | get_json_string_value status)" while [[ "${reqstatus}" = "pending" ]] || [[ "${reqstatus}" = "processing" ]]; do sleep 1 if [[ "${API}" -eq 2 ]]; then result="$(signed_request "${challenge_uris[${idx}]}" "" | jsonsh)" else result="$(http_request get "${challenge_uris[${idx}]}" | jsonsh)" fi reqstatus="$(echo "${result}" | get_json_string_value status)" done [[ "${CHALLENGETYPE}" = "http-01" ]] && rm -f "${WELLKNOWN}/${challenge_tokens[${idx}]}" [[ "${CHALLENGETYPE}" = "tls-alpn-01" ]] && rm -f "${ALPNCERTDIR}/${challenge_names[${idx}]}.crt.pem" "${ALPNCERTDIR}/${challenge_names[${idx}]}.key.pem" if [[ "${reqstatus}" = "valid" ]]; then echo " + Challenge is valid!" else [[ -n "${HOOK}" ]] && ("${HOOK}" "invalid_challenge" "${altname}" "${result}" || _exiterr 'invalid_challenge hook returned with non-zero exit code') break fi idx=$((idx+1)) done if [[ ${num_pending_challenges} -ne 0 ]]; then echo " + Cleaning challenge tokens..." # Clean challenge tokens using chained hook # shellcheck disable=SC2068 [[ -n "${HOOK}" ]] && [[ "${HOOK_CHAIN}" = "yes" ]] && ("${HOOK}" "clean_challenge" ${deploy_args[@]} || _exiterr 'clean_challenge hook returned with non-zero exit code') # Clean remaining challenge tokens if validation has failed local idx=0 while [ ${idx} -lt ${num_pending_challenges} ]; do # Delete challenge file [[ "${CHALLENGETYPE}" = "http-01" ]] && rm -f "${WELLKNOWN}/${challenge_tokens[${idx}]}" # Delete alpn verification certificates [[ "${CHALLENGETYPE}" = "tls-alpn-01" ]] && rm -f "${ALPNCERTDIR}/${challenge_names[${idx}]}.crt.pem" "${ALPNCERTDIR}/${challenge_names[${idx}]}.key.pem" # Clean challenge token using non-chained hook # shellcheck disable=SC2086 [[ -n "${HOOK}" ]] && [[ "${HOOK_CHAIN}" != "yes" ]] && ("${HOOK}" "clean_challenge" ${deploy_args[${idx}]} || _exiterr 'clean_challenge hook returned with non-zero exit code') idx=$((idx+1)) done if [[ "${reqstatus}" != "valid" ]]; then echo " + Challenge validation has failed :(" _exiterr "Challenge is invalid! (returned: ${reqstatus}) (result: ${result})" fi fi # Finally request certificate from the acme-server and store it in cert-${timestamp}.pem and link from cert.pem echo " + Requesting certificate..." csr64="$("${OPENSSL}" req -in "${csrfile}" -config "${OPENSSL_CNF}" -outform DER | urlbase64)" if [[ ${API} -eq 1 ]]; then crt64="$(signed_request "${CA_NEW_CERT}" '{"resource": "new-cert", "csr": "'"${csr64}"'"}' | "${OPENSSL}" base64 -e)" crt="$( printf -- '-----BEGIN CERTIFICATE-----\n%s\n-----END CERTIFICATE-----\n' "${crt64}" )" else result="$(signed_request "${finalize}" '{"csr": "'"${csr64}"'"}' | jsonsh)" waited=0 while :; do orderstatus="$(echo "${result}" | get_json_string_value status)" case "${orderstatus}" in "processing" | "pending") if [ ${ORDER_TIMEOUT} -gt 0 ] && [ ${waited} -gt ${ORDER_TIMEOUT} ]; then _exiterr "Timed out waiting for processing of order (still ${orderstatus})" fi echo " + Order is ${orderstatus}..." sleep 2; waited=$((waited+2)) ;; "valid") break; ;; *) _exiterr "Order has invalid/unknown status: ${orderstatus}" ;; esac result="$(signed_request "${order_location}" "" | jsonsh)" done resheaders="$(_mktemp)" certificate="$(echo "${result}" | get_json_string_value certificate)" crt="$(signed_request "${certificate}" "" 4>"${resheaders}")" if [ -n "${PREFERRED_CHAIN:-}" ]; then foundaltchain=0 altcn="$(get_last_cn "${crt}")" altoptions="${altcn}" if [ "${altcn}" = "${PREFERRED_CHAIN}" ]; then foundaltchain=1 fi if [ "${foundaltchain}" = "0" ] && (grep -Ei '^link:' "${resheaders}" | grep -q -Ei 'rel="alternate"'); then while read -r altcrturl; do if [ "${foundaltchain}" = "0" ]; then altcrt="$(signed_request "${altcrturl}" "")" altcn="$(get_last_cn "${altcrt}")" altoptions="${altoptions}, ${altcn}" if [ "${altcn}" = "${PREFERRED_CHAIN}" ]; then foundaltchain=1 crt="${altcrt}" fi fi done <<< "$(grep -Ei '^link:' "${resheaders}" | grep -Ei 'rel="alternate"' | cut -d'<' -f2 | cut -d'>' -f1)" fi if [ "${foundaltchain}" = "0" ]; then _exiterr "Alternative chain with CN = ${PREFERRED_CHAIN} not found, available options: ${altoptions}" fi echo " + Using preferred chain with CN = ${altcn}" fi rm -f "${resheaders}" fi # Try to load the certificate to detect corruption echo " + Checking certificate..." _openssl x509 -text <<<"${crt}" echo "${crt}" >&3 unset challenge_token echo " + Done!" } # grep issuer cert uri from certificate get_issuer_cert_uri() { certificate="${1}" "${OPENSSL}" x509 -in "${certificate}" -noout -text | (grep 'CA Issuers - URI:' | cut -d':' -f2-) || true } get_issuer_hash() { certificate="${1}" "${OPENSSL}" x509 -in "${certificate}" -noout -issuer_hash } get_ocsp_url() { certificate="${1}" "${OPENSSL}" x509 -in "${certificate}" -noout -ocsp_uri } # walk certificate chain, retrieving all intermediate certificates walk_chain() { local certificate certificate="${1}" local issuer_cert_uri issuer_cert_uri="${2:-}" if [[ -z "${issuer_cert_uri}" ]]; then issuer_cert_uri="$(get_issuer_cert_uri "${certificate}")"; fi if [[ -n "${issuer_cert_uri}" ]]; then # create temporary files local tmpcert local tmpcert_raw tmpcert_raw="$(_mktemp)" tmpcert="$(_mktemp)" # download certificate http_request get "${issuer_cert_uri}" > "${tmpcert_raw}" # PEM if grep -q "BEGIN CERTIFICATE" "${tmpcert_raw}"; then mv "${tmpcert_raw}" "${tmpcert}" # DER elif "${OPENSSL}" x509 -in "${tmpcert_raw}" -inform DER -out "${tmpcert}" -outform PEM 2> /dev/null > /dev/null; then : # PKCS7 elif "${OPENSSL}" pkcs7 -in "${tmpcert_raw}" -inform DER -out "${tmpcert}" -outform PEM -print_certs 2> /dev/null > /dev/null; then : # Unknown certificate type else _exiterr "Unknown certificate type in chain" fi local next_issuer_cert_uri next_issuer_cert_uri="$(get_issuer_cert_uri "${tmpcert}")" if [[ -n "${next_issuer_cert_uri}" ]]; then printf "\n%s\n" "${issuer_cert_uri}" cat "${tmpcert}" walk_chain "${tmpcert}" "${next_issuer_cert_uri}" fi rm -f "${tmpcert}" "${tmpcert_raw}" fi } # Generate ALPN verification certificate generate_alpn_certificate() { local altname="${1}" local identifier_type="${2}" local acmevalidation="${3}" local alpncertdir="${ALPNCERTDIR}" if [[ ! -e "${alpncertdir}" ]]; then echo " + Creating new directory ${alpncertdir} ..." mkdir -p "${alpncertdir}" || _exiterr "Unable to create directory ${alpncertdir}" fi echo " + Generating ALPN certificate and key for ${1}..." tmp_openssl_cnf="$(_mktemp)" cat "${OPENSSL_CNF}" > "${tmp_openssl_cnf}" if [[ "${identifier_type}" = "ip" ]]; then printf "\n[SAN]\nsubjectAltName=IP:%s\n" "${altname}" >> "${tmp_openssl_cnf}" else printf "\n[SAN]\nsubjectAltName=DNS:%s\n" "${altname}" >> "${tmp_openssl_cnf}" fi printf "1.3.6.1.5.5.7.1.31=critical,DER:04:20:%s\n" "${acmevalidation}" >> "${tmp_openssl_cnf}" SUBJ="/CN=${altname}/" [[ "${OSTYPE:0:5}" = "MINGW" ]] && SUBJ="/${SUBJ}" if [[ "${identifier_type}" = "ip" ]]; then altname="$(echo "${altname}" | ip_to_ptr)" fi _openssl req -x509 -new -sha256 -nodes -newkey rsa:2048 -keyout "${alpncertdir}/${altname}.key.pem" -out "${alpncertdir}/${altname}.crt.pem" -subj "${SUBJ}" -extensions SAN -config "${tmp_openssl_cnf}" chmod g+r "${alpncertdir}/${altname}.key.pem" "${alpncertdir}/${altname}.crt.pem" rm -f "${tmp_openssl_cnf}" } # Create certificate for domain(s) sign_domain() { local certdir="${1}" shift timestamp="${1}" shift domain="${1}" altnames="${*}" export altnames echo " + Signing domains..." if [[ ${API} -eq 1 ]]; then if [[ -z "${CA_NEW_AUTHZ}" ]] || [[ -z "${CA_NEW_CERT}" ]]; then _exiterr "Certificate authority doesn't allow certificate signing" fi elif [[ ${API} -eq 2 ]] && [[ -z "${CA_NEW_ORDER}" ]]; then _exiterr "Certificate authority doesn't allow certificate signing" fi local privkey="privkey.pem" if [[ ! -e "${certdir}/cert-${timestamp}.csr" ]]; then # generate a new private key if we need or want one if [[ ! -r "${certdir}/privkey.pem" ]] || [[ "${PRIVATE_KEY_RENEW}" = "yes" ]]; then echo " + Generating private key..." privkey="privkey-${timestamp}.pem" local tmp_privkey tmp_privkey="$(_mktemp)" case "${KEY_ALGO}" in rsa) _openssl genrsa -out "${tmp_privkey}" "${KEYSIZE}";; prime256v1|secp384r1) _openssl ecparam -genkey -name "${KEY_ALGO}" -out "${tmp_privkey}" -noout;; esac cat "${tmp_privkey}" > "${certdir}/privkey-${timestamp}.pem" rm "${tmp_privkey}" fi # move rolloverkey into position (if any) if [[ -r "${certdir}/privkey.pem" && -r "${certdir}/privkey.roll.pem" && "${PRIVATE_KEY_RENEW}" = "yes" && "${PRIVATE_KEY_ROLLOVER}" = "yes" ]]; then echo " + Moving Rolloverkey into position.... " mv "${certdir}/privkey.roll.pem" "${certdir}/privkey-tmp.pem" mv "${certdir}/privkey-${timestamp}.pem" "${certdir}/privkey.roll.pem" mv "${certdir}/privkey-tmp.pem" "${certdir}/privkey-${timestamp}.pem" fi # generate a new private rollover key if we need or want one if [[ ! -r "${certdir}/privkey.roll.pem" && "${PRIVATE_KEY_ROLLOVER}" = "yes" && "${PRIVATE_KEY_RENEW}" = "yes" ]]; then echo " + Generating private rollover key..." case "${KEY_ALGO}" in rsa) _openssl genrsa -out "${certdir}/privkey.roll.pem" "${KEYSIZE}";; prime256v1|secp384r1) _openssl ecparam -genkey -name "${KEY_ALGO}" -out "${certdir}/privkey.roll.pem" -noout;; esac fi # delete rolloverkeys if disabled if [[ -r "${certdir}/privkey.roll.pem" && ! "${PRIVATE_KEY_ROLLOVER}" = "yes" ]]; then echo " + Removing Rolloverkey (feature disabled)..." rm -f "${certdir}/privkey.roll.pem" fi # Generate signing request config and the actual signing request echo " + Generating signing request..." SAN="" for altname in ${altnames}; do if [[ "${altname}" =~ ^ip: ]]; then SAN="${SAN}IP:${altname:3}, " else SAN="${SAN}DNS:${altname}, " fi done if [[ "${domain}" =~ ^ip: ]]; then SUBJ="/CN=${domain:3}/" else SUBJ="/CN=${domain}/" fi SAN="${SAN%%, }" local tmp_openssl_cnf tmp_openssl_cnf="$(_mktemp)" cat "${OPENSSL_CNF}" > "${tmp_openssl_cnf}" printf "\n[SAN]\nsubjectAltName=%s" "${SAN}" >> "${tmp_openssl_cnf}" if [ "${OCSP_MUST_STAPLE}" = "yes" ]; then printf "\n1.3.6.1.5.5.7.1.24=DER:30:03:02:01:05" >> "${tmp_openssl_cnf}" fi if [[ "${OSTYPE:0:5}" = "MINGW" ]]; then # The subject starts with a /, so MSYS will assume it's a path and convert # it unless we escape it with another one: SUBJ="/${SUBJ}" fi "${OPENSSL}" req -new -sha256 -key "${certdir}/${privkey}" -out "${certdir}/cert-${timestamp}.csr" -subj "${SUBJ}" -reqexts SAN -config "${tmp_openssl_cnf}" rm -f "${tmp_openssl_cnf}" fi crt_path="${certdir}/cert-${timestamp}.pem" # shellcheck disable=SC2086 sign_csr "${certdir}/cert-${timestamp}.csr" ${altnames} 3>"${crt_path}" # Create fullchain.pem echo " + Creating fullchain.pem..." if [[ ${API} -eq 1 ]]; then cat "${crt_path}" > "${certdir}/fullchain-${timestamp}.pem" local issuer_hash issuer_hash="$(get_issuer_hash "${crt_path}")" if [ -e "${CHAINCACHE}/${issuer_hash}.chain" ]; then echo " + Using cached chain!" cat "${CHAINCACHE}/${issuer_hash}.chain" > "${certdir}/chain-${timestamp}.pem" else echo " + Walking chain..." local issuer_cert_uri issuer_cert_uri="$(get_issuer_cert_uri "${crt_path}" || echo "unknown")" (walk_chain "${crt_path}" > "${certdir}/chain-${timestamp}.pem") || _exiterr "Walking chain has failed, your certificate has been created and can be found at ${crt_path}, the corresponding private key at ${privkey}. If you want you can manually continue on creating and linking all necessary files. If this error occurs again you should manually generate the certificate chain and place it under ${CHAINCACHE}/${issuer_hash}.chain (see ${issuer_cert_uri})" cat "${certdir}/chain-${timestamp}.pem" > "${CHAINCACHE}/${issuer_hash}.chain" fi cat "${certdir}/chain-${timestamp}.pem" >> "${certdir}/fullchain-${timestamp}.pem" else tmpcert="$(_mktemp)" tmpchain="$(_mktemp)" awk '{print >out}; /----END CERTIFICATE-----/{out=tmpchain}' out="${tmpcert}" tmpchain="${tmpchain}" "${certdir}/cert-${timestamp}.pem" mv "${certdir}/cert-${timestamp}.pem" "${certdir}/fullchain-${timestamp}.pem" cat "${tmpcert}" > "${certdir}/cert-${timestamp}.pem" cat "${tmpchain}" > "${certdir}/chain-${timestamp}.pem" rm "${tmpcert}" "${tmpchain}" fi # Wait for hook script to sync the files before creating the symlinks [[ -n "${HOOK}" ]] && ("${HOOK}" "sync_cert" "${certdir}/privkey-${timestamp}.pem" "${certdir}/cert-${timestamp}.pem" "${certdir}/fullchain-${timestamp}.pem" "${certdir}/chain-${timestamp}.pem" "${certdir}/cert-${timestamp}.csr" || _exiterr 'sync_cert hook returned with non-zero exit code') # Update symlinks [[ "${privkey}" = "privkey.pem" ]] || ln -sf "privkey-${timestamp}.pem" "${certdir}/privkey.pem" ln -sf "chain-${timestamp}.pem" "${certdir}/chain.pem" ln -sf "fullchain-${timestamp}.pem" "${certdir}/fullchain.pem" ln -sf "cert-${timestamp}.csr" "${certdir}/cert.csr" ln -sf "cert-${timestamp}.pem" "${certdir}/cert.pem" # Wait for hook script to clean the challenge and to deploy cert if used [[ -n "${HOOK}" ]] && ("${HOOK}" "deploy_cert" "${domain}" "${certdir}/privkey.pem" "${certdir}/cert.pem" "${certdir}/fullchain.pem" "${certdir}/chain.pem" "${timestamp}" || _exiterr 'deploy_cert hook returned with non-zero exit code') unset challenge_token echo " + Done!" } # Usage: --version (-v) # Description: Print version information command_version() { load_config noverify echo "Dehydrated by Lukas Schauer" echo "https://dehydrated.io" echo "" echo "Dehydrated version: ${VERSION}" revision="$(cd "${SCRIPTDIR}"; git rev-parse HEAD 2>/dev/null || echo "unknown")" echo "GIT-Revision: ${revision}" echo "" # shellcheck disable=SC1091 if [[ "${OSTYPE}" =~ (BSD|Darwin) ]]; then echo "OS: $(uname -sr)" elif [[ -e /etc/os-release ]]; then ( . /etc/os-release && echo "OS: $PRETTY_NAME" ) elif [[ -e /usr/lib/os-release ]]; then ( . /usr/lib/os-release && echo "OS: $PRETTY_NAME" ) else echo "OS: $(grep -v '^$' /etc/issue | head -n1 | _sed 's/\\(r|n|l) .*//g')" fi echo "Used software:" [[ -n "${BASH_VERSION:-}" ]] && echo " bash: ${BASH_VERSION}" [[ -n "${ZSH_VERSION:-}" ]] && echo " zsh: ${ZSH_VERSION}" echo " curl: ${CURL_VERSION}" if [[ "${OSTYPE}" =~ (BSD|Darwin) ]]; then echo " awk, sed, mktemp, grep, diff: BSD base system versions" else echo " awk: $(awk -W version 2>&1 | head -n1)" echo " sed: $(sed --version 2>&1 | head -n1)" echo " mktemp: $(mktemp --version 2>&1 | head -n1)" echo " grep: $(grep --version 2>&1 | head -n1)" echo " diff: $(diff --version 2>&1 | head -n1)" fi echo " openssl: $("${OPENSSL}" version 2>&1)" exit 0 } # Usage: --display-terms # Description: Display current terms of service command_terms() { init_system echo "The current terms of service: $CA_TERMS" echo "+ Done!" exit 0 } # Usage: --register # Description: Register account key command_register() { init_system echo "+ Done!" exit 0 } # Usage: --account # Description: Update account contact information command_account() { init_system FAILED=false NEW_ACCOUNT_KEY_JSON="$(_mktemp)" # Check if we have the registration url if [[ -z "${ACCOUNT_URL}" ]]; then _exiterr "Error retrieving registration url." fi echo "+ Updating registration url: ${ACCOUNT_URL} contact information..." if [[ ${API} -eq 1 ]]; then # If an email for the contact has been provided then adding it to the registered account if [[ -n "${CONTACT_EMAIL}" ]]; then (signed_request "${ACCOUNT_URL}" '{"resource": "reg", "contact":["mailto:'"${CONTACT_EMAIL}"'"]}' > "${NEW_ACCOUNT_KEY_JSON}") || FAILED=true else (signed_request "${ACCOUNT_URL}" '{"resource": "reg", "contact":[]}' > "${NEW_ACCOUNT_KEY_JSON}") || FAILED=true fi else # If an email for the contact has been provided then adding it to the registered account if [[ -n "${CONTACT_EMAIL}" ]]; then (signed_request "${ACCOUNT_URL}" '{"contact":["mailto:'"${CONTACT_EMAIL}"'"]}' > "${NEW_ACCOUNT_KEY_JSON}") || FAILED=true else (signed_request "${ACCOUNT_URL}" '{"contact":[]}' > "${NEW_ACCOUNT_KEY_JSON}") || FAILED=true fi fi if [[ "${FAILED}" = "true" ]]; then rm "${NEW_ACCOUNT_KEY_JSON}" _exiterr "Error updating account information. See message above for more information." fi if diff -q "${NEW_ACCOUNT_KEY_JSON}" "${ACCOUNT_KEY_JSON}" > /dev/null; then echo "+ Account information was the same after the update" rm "${NEW_ACCOUNT_KEY_JSON}" else ACCOUNT_KEY_JSON_BACKUP="${ACCOUNT_KEY_JSON%.*}-$(date +%s).json" echo "+ Backup ${ACCOUNT_KEY_JSON} as ${ACCOUNT_KEY_JSON_BACKUP}" cp -p "${ACCOUNT_KEY_JSON}" "${ACCOUNT_KEY_JSON_BACKUP}" echo "+ Populate ${ACCOUNT_KEY_JSON}" mv "${NEW_ACCOUNT_KEY_JSON}" "${ACCOUNT_KEY_JSON}" fi echo "+ Done!" exit 0 } # Parse contents of domains.txt and domains.txt.d parse_domains_txt() { # Allow globbing temporarily noglob_set local inputs=("${DOMAINS_TXT}" "${DOMAINS_TXT}.d"/*.txt) noglob_clear cat "${inputs[@]}" | tr -d '\r' | awk '{print tolower($0)}' | _sed -e 's/^[[:space:]]*//g' -e 's/[[:space:]]*$//g' -e 's/[[:space:]]+/ /g' -e 's/([^ ])>/\1 >/g' -e 's/> />/g' | (grep -vE '^(#|$)' || true) } # Usage: --cron (-c) # Description: Sign/renew non-existent/changed/expiring certificates. command_sign_domains() { init_system hookscript_bricker_hook # Call startup hook [[ -n "${HOOK}" ]] && ("${HOOK}" "startup_hook" || _exiterr 'startup_hook hook returned with non-zero exit code') if [ ! -d "${CHAINCACHE}" ]; then echo " + Creating chain cache directory ${CHAINCACHE}" mkdir "${CHAINCACHE}" fi if [[ -n "${PARAM_DOMAIN:-}" ]]; then DOMAINS_TXT="$(_mktemp)" if [[ -n "${PARAM_ALIAS:-}" ]]; then printf "%s > %s" "${PARAM_DOMAIN}" "${PARAM_ALIAS}" > "${DOMAINS_TXT}" else printf "%s" "${PARAM_DOMAIN}" > "${DOMAINS_TXT}" fi elif [[ -e "${DOMAINS_TXT}" ]]; then if [[ ! -r "${DOMAINS_TXT}" ]]; then _exiterr "domains.txt found but not readable" fi else _exiterr "domains.txt not found and --domain not given" fi # Generate certificates for all domains found in domains.txt. Check if existing certificate are about to expire ORIGIFS="${IFS}" IFS=$'\n' for line in $(parse_domains_txt); do reset_configvars IFS="${ORIGIFS}" alias="$(grep -Eo '>[^ ]+' <<< "${line}" || true)" line="$(_sed -e 's/>[^ ]+[ ]*//g' <<< "${line}")" aliascount="$(grep -Eo '>' <<< "${alias}" | awk 'END {print NR}' || true )" [ "${aliascount}" -gt 1 ] && _exiterr "Only one alias per line is allowed in domains.txt!" domain="$(printf '%s\n' "${line}" | cut -d' ' -f1)" morenames="$(printf '%s\n' "${line}" | cut -s -d' ' -f2-)" [ "${aliascount}" -lt 1 ] && alias="${domain}" || alias="${alias#>}" export alias if [[ -z "${morenames}" ]];then echo "Processing ${domain}" else echo "Processing ${domain} with alternative names: ${morenames}" fi if [ "${alias:0:2}" = "*." ]; then _exiterr "Please define a valid alias for your ${domain} wildcard-certificate. See domains.txt-documentation for more details." fi local certdir="${CERTDIR}/${alias}" cert="${certdir}/cert.pem" chain="${certdir}/chain.pem" force_renew="${PARAM_FORCE:-no}" timestamp="$(date +%s)" # If there is no existing certificate directory => make it if [[ ! -e "${certdir}" ]]; then echo " + Creating new directory ${certdir} ..." mkdir -p "${certdir}" || _exiterr "Unable to create directory ${certdir}" fi # read cert config # for now this loads the certificate specific config in a subshell and parses a diff of set variables. # we could just source the config file but i decided to go this way to protect people from accidentally overriding # variables used internally by this script itself. if [[ -n "${DOMAINS_D}" ]]; then certconfig="${DOMAINS_D}/${alias}" else certconfig="${certdir}/config" fi if [ -f "${certconfig}" ]; then echo " + Using certificate specific config file!" ORIGIFS="${IFS}" IFS=$'\n' for cfgline in $( beforevars="$(_mktemp)" aftervars="$(_mktemp)" set > "${beforevars}" # shellcheck disable=SC1090 . "${certconfig}" set > "${aftervars}" diff -u "${beforevars}" "${aftervars}" | grep -E '^\+[^+]' rm "${beforevars}" rm "${aftervars}" ); do config_var="$(echo "${cfgline:1}" | cut -d'=' -f1)" config_value="$(echo "${cfgline:1}" | cut -d'=' -f2- | tr -d "'")" # All settings that are allowed here should also be stored and # restored in store_configvars() and reset_configvars() case "${config_var}" in KEY_ALGO|OCSP_MUST_STAPLE|OCSP_FETCH|OCSP_DAYS|PRIVATE_KEY_RENEW|PRIVATE_KEY_ROLLOVER|KEYSIZE|CHALLENGETYPE|HOOK|PREFERRED_CHAIN|WELLKNOWN|HOOK_CHAIN|OPENSSL_CNF|RENEW_DAYS|ACME_PROFILE|ORDER_TIMEOUT) echo " + ${config_var} = ${config_value}" declare -- "${config_var}=${config_value}" ;; _) ;; *) echo " ! Setting ${config_var} on a per-certificate base is not (yet) supported" >&2 esac done IFS="${ORIGIFS}" fi verify_config hookscript_bricker_hook export WELLKNOWN CHALLENGETYPE KEY_ALGO PRIVATE_KEY_ROLLOVER skip="no" # Allow for external CSR generation local csrfile="" if [[ -n "${HOOK}" ]]; then csr="$("${HOOK}" "generate_csr" "${domain}" "${certdir}" "${domain} ${morenames}")" || _exiterr 'generate_csr hook returned with non-zero exit code' if grep -qE "\-----BEGIN (NEW )?CERTIFICATE REQUEST-----" <<< "${csr}"; then csrfile="$(_mktemp)" cat > "${csrfile}" <<< "${csr}" altnames="$(extract_altnames "${csrfile}")" domain="$(cut -d' ' -f1 <<< "${altnames}")" morenames="$(cut -s -d' ' -f2- <<< "${altnames}")" echo " + Using CSR from hook script (real names: ${altnames})" else csrfile="" fi fi # Check domain names of existing certificate if [[ -e "${cert}" && "${force_renew}" = "no" ]]; then printf " + Checking domain name(s) of existing cert..." certnames="$("${OPENSSL}" x509 -in "${cert}" -text -noout | grep -E '(DNS|IP( Address*)):' | _sed 's/(DNS|IP( Address)*)://g' | tr -d ' ' | tr ',' '\n' | sort -u | tr '\n' ' ' | _sed 's/ $//')" givennames="$(echo "${domain}" "${morenames}"| tr ' ' '\n' | sort -u | tr '\n' ' ' | _sed 's/ip://g' | _sed 's/ $//' | _sed 's/^ //')" if [[ "${certnames}" = "${givennames}" ]]; then echo " unchanged." else echo " changed!" echo " + Domain name(s) are not matching!" echo " + Names in old certificate: ${certnames}" echo " + Configured names: ${givennames}" echo " + Forcing renew." force_renew="yes" fi fi # Check expire date of existing certificate if [[ -e "${cert}" ]]; then echo " + Checking expire date of existing cert..." valid="$("${OPENSSL}" x509 -enddate -noout -in "${cert}" | cut -d= -f2- )" printf " + Valid till %s " "${valid}" if ("${OPENSSL}" x509 -checkend $((RENEW_DAYS * 86400)) -noout -in "${cert}" > /dev/null 2>&1); then printf "(Longer than %d days). " "${RENEW_DAYS}" if [[ "${force_renew}" = "yes" ]]; then echo "Ignoring because renew was forced!" else # Certificate-Names unchanged and cert is still valid echo "Skipping renew!" [[ -n "${HOOK}" ]] && ("${HOOK}" "unchanged_cert" "${domain}" "${certdir}/privkey.pem" "${certdir}/cert.pem" "${certdir}/fullchain.pem" "${certdir}/chain.pem" || _exiterr 'unchanged_cert hook returned with non-zero exit code') skip="yes" fi else echo "(Less than ${RENEW_DAYS} days). Renewing!" fi fi local update_ocsp update_ocsp="no" # Sign certificate for this domain if [[ ! "${skip}" = "yes" ]]; then update_ocsp="yes" if [[ -n "${csrfile}" ]]; then cat "${csrfile}" > "${certdir}/cert-${timestamp}.csr" rm "${csrfile}" fi # shellcheck disable=SC2086 if [[ "${PARAM_KEEP_GOING:-}" = "yes" ]]; then skip_exit_hook=yes sign_domain "${certdir}" "${timestamp}" "${domain}" ${morenames} & wait $! || exit_with_errorcode=1 skip_exit_hook=no else sign_domain "${certdir}" "${timestamp}" "${domain}" ${morenames} fi fi if [[ "${OCSP_FETCH}" = "yes" ]]; then local ocsp_url ocsp_url="$(get_ocsp_url "${cert}")" if [[ ! -e "${certdir}/ocsp.der" ]]; then update_ocsp="yes" elif ! ("${OPENSSL}" ocsp -no_nonce -issuer "${chain}" -verify_other "${chain}" -cert "${cert}" -respin "${certdir}/ocsp.der" -status_age $((OCSP_DAYS*24*3600)) 2>&1 | grep -q "${cert}: good"); then update_ocsp="yes" fi if [[ "${update_ocsp}" = "yes" ]]; then echo " + Updating OCSP stapling file" ocsp_timestamp="$(date +%s)" if grep -qE "^(openssl (0|(1\.0))\.)|(libressl (1|2|3)\.)" <<< "$(${OPENSSL} version | awk '{print tolower($0)}')"; then ocsp_log="$("${OPENSSL}" ocsp -no_nonce -issuer "${chain}" -verify_other "${chain}" -cert "${cert}" -respout "${certdir}/ocsp-${ocsp_timestamp}.der" -url "${ocsp_url}" -header "HOST" "$(echo "${ocsp_url}" | _sed -e 's/^http(s?):\/\///' -e 's/\/.*$//g')" 2>&1)" || _exiterr "Error while fetching OCSP information: ${ocsp_log}" else ocsp_log="$("${OPENSSL}" ocsp -no_nonce -issuer "${chain}" -verify_other "${chain}" -cert "${cert}" -respout "${certdir}/ocsp-${ocsp_timestamp}.der" -url "${ocsp_url}" 2>&1)" || _exiterr "Error while fetching OCSP information: ${ocsp_log}" fi ln -sf "ocsp-${ocsp_timestamp}.der" "${certdir}/ocsp.der" [[ -n "${HOOK}" ]] && (altnames="${domain} ${morenames}" "${HOOK}" "deploy_ocsp" "${domain}" "${certdir}/ocsp.der" "${ocsp_timestamp}" || _exiterr 'deploy_ocsp hook returned with non-zero exit code') else echo " + OCSP stapling file is still valid (skipping update)" fi fi done reset_configvars # remove temporary domains.txt file if used [[ -n "${PARAM_DOMAIN:-}" ]] && rm -f "${DOMAINS_TXT}" [[ -n "${HOOK}" ]] && ("${HOOK}" "exit_hook" || echo 'exit_hook returned with non-zero exit code!' >&2) if [[ "${AUTO_CLEANUP}" == "yes" ]]; then echo " + Running automatic cleanup" PARAM_CLEANUPDELETE="${AUTO_CLEANUP_DELETE:-no}" command_cleanup noinit | _sed 's/^/ + /g' fi exit "${exit_with_errorcode}" } # Usage: --signcsr (-s) path/to/csr.pem # Description: Sign a given CSR, output CRT on stdout (advanced usage) command_sign_csr() { init_system # redirect stdout to stderr # leave stdout over at fd 3 to output the cert exec 3>&1 1>&2 # load csr local csrfile="${1}" if [ ! -r "${csrfile}" ]; then _exiterr "Could not read certificate signing request ${csrfile}" fi # extract names altnames="$(extract_altnames "${csrfile}")" # gen cert certfile="$(_mktemp)" # shellcheck disable=SC2086 sign_csr "${csrfile}" ${altnames} 3> "${certfile}" # print cert echo "# CERT #" >&3 cat "${certfile}" >&3 echo >&3 # print chain if [ -n "${PARAM_FULL_CHAIN:-}" ]; then # get and convert ca cert chainfile="$(_mktemp)" tmpchain="$(_mktemp)" http_request get "$("${OPENSSL}" x509 -in "${certfile}" -noout -text | grep 'CA Issuers - URI:' | cut -d':' -f2-)" > "${tmpchain}" if grep -q "BEGIN CERTIFICATE" "${tmpchain}"; then mv "${tmpchain}" "${chainfile}" else "${OPENSSL}" x509 -in "${tmpchain}" -inform DER -out "${chainfile}" -outform PEM rm "${tmpchain}" fi echo "# CHAIN #" >&3 cat "${chainfile}" >&3 rm "${chainfile}" fi # cleanup rm "${certfile}" exit 0 } # Usage: --revoke (-r) path/to/cert.pem # Description: Revoke specified certificate command_revoke() { init_system [[ -n "${CA_REVOKE_CERT}" ]] || _exiterr "Certificate authority doesn't allow certificate revocation." cert="${1}" if [[ -L "${cert}" ]]; then # follow symlink and use real certificate name (so we move the real file and not the symlink at the end) local link_target link_target="$(readlink -n "${cert}")" if [[ "${link_target}" =~ ^/ ]]; then cert="${link_target}" else cert="$(dirname "${cert}")/${link_target}" fi fi [[ -f "${cert}" ]] || _exiterr "Could not find certificate ${cert}" echo "Revoking ${cert}" cert64="$("${OPENSSL}" x509 -in "${cert}" -inform PEM -outform DER | urlbase64)" if [[ ${API} -eq 1 ]]; then response="$(signed_request "${CA_REVOKE_CERT}" '{"resource": "revoke-cert", "certificate": "'"${cert64}"'"}' | clean_json)" else response="$(signed_request "${CA_REVOKE_CERT}" '{"certificate": "'"${cert64}"'"}' | clean_json)" fi # if there is a problem with our revoke request _request (via signed_request) will report this and "exit 1" out # so if we are here, it is safe to assume the request was successful echo " + Done." echo " + Renaming certificate to ${cert}-revoked" mv -f "${cert}" "${cert}-revoked" } # Usage: --deactivate # Description: Deactivate account command_deactivate() { init_system echo "Deactivating account ${ACCOUNT_URL}" if [[ ${API} -eq 1 ]]; then echo "Deactivation for ACMEv1 is not implemented" else response="$(signed_request "${ACCOUNT_URL}" '{"status": "deactivated"}' | clean_json)" deactstatus=$(echo "$response" | jsonsh | get_json_string_value "status") if [[ "${deactstatus}" = "deactivated" ]]; then touch "${ACCOUNT_DEACTIVATED}" else _exiterr "Account deactivation failed!" fi fi echo " + Done." } # Usage: --cleanup (-gc) # Description: Move unused certificate files to archive directory command_cleanup() { if [ ! "${1:-}" = "noinit" ]; then load_config fi if [[ ! "${PARAM_CLEANUPDELETE:-}" = "yes" ]]; then # Create global archive directory if not existent if [[ ! -e "${BASEDIR}/archive" ]]; then mkdir "${BASEDIR}/archive" fi fi # Allow globbing noglob_set # Loop over all certificate directories for certdir in "${CERTDIR}/"*; do # Skip if entry is not a folder [[ -d "${certdir}" ]] || continue # Get certificate name certname="$(basename "${certdir}")" # Create certificates archive directory if not existent if [[ ! "${PARAM_CLEANUPDELETE:-}" = "yes" ]]; then archivedir="${BASEDIR}/archive/${certname}" if [[ ! -e "${archivedir}" ]]; then mkdir "${archivedir}" fi fi # Loop over file-types (certificates, keys, signing-requests, ...) for filetype in cert.csr cert.pem chain.pem fullchain.pem privkey.pem ocsp.der; do # Delete all if symlink is broken if [[ -r "${certdir}/${filetype}" ]]; then # Look up current file in use current="$(basename "$(readlink "${certdir}/${filetype}")")" else if [[ -h "${certdir}/${filetype}" ]]; then echo "Removing broken symlink: ${certdir}/${filetype}" rm -f "${certdir}/${filetype}" fi current="" fi # Split filetype into name and extension filebase="$(echo "${filetype}" | cut -d. -f1)" fileext="$(echo "${filetype}" | cut -d. -f2)" # Loop over all files of this type for file in "${certdir}/${filebase}-"*".${fileext}" "${certdir}/${filebase}-"*".${fileext}-revoked"; do # Check if current file is in use, if unused move to archive directory filename="$(basename "${file}")" if [[ ! "${filename}" = "${current}" ]] && [[ -f "${certdir}/${filename}" ]]; then if [[ "${PARAM_CLEANUPDELETE:-}" = "yes" ]]; then echo "Deleting unused file: ${certname}/${filename}" rm "${certdir}/${filename}" else echo "Moving unused file to archive directory: ${certname}/${filename}" mv "${certdir}/${filename}" "${archivedir}/${filename}" fi fi done done done exit "${exit_with_errorcode}" } # Usage: --cleanup-delete (-gcd) # Description: Deletes (!) unused certificate files command_cleanupdelete() { command_cleanup } # Usage: --help (-h) # Description: Show help text command_help() { printf "Usage: %s [-h] [command [argument]] [parameter [argument]] [parameter [argument]] ...\n\n" "${0}" printf "Default command: help\n\n" echo "Commands:" grep -e '^[[:space:]]*# Usage:' -e '^[[:space:]]*# Description:' -e '^command_.*()[[:space:]]*{' "${0}" | while read -r usage; read -r description; read -r command; do if [[ ! "${usage}" =~ Usage ]] || [[ ! "${description}" =~ Description ]] || [[ ! "${command}" =~ ^command_ ]]; then _exiterr "Error generating help text." fi printf " %-32s %s\n" "${usage##"# Usage: "}" "${description##"# Description: "}" done printf -- "\nParameters:\n" grep -E -e '^[[:space:]]*# PARAM_Usage:' -e '^[[:space:]]*# PARAM_Description:' "${0}" | while read -r usage; read -r description; do if [[ ! "${usage}" =~ Usage ]] || [[ ! "${description}" =~ Description ]]; then _exiterr "Error generating help text." fi printf " %-32s %s\n" "${usage##"# PARAM_Usage: "}" "${description##"# PARAM_Description: "}" done } # Usage: --env (-e) # Description: Output configuration variables for use in other scripts command_env() { echo "# dehydrated configuration" load_config typeset -p CA CERTDIR ALPNCERTDIR CHALLENGETYPE DOMAINS_D DOMAINS_TXT HOOK HOOK_CHAIN RENEW_DAYS ACCOUNT_KEY ACCOUNT_KEY_JSON ACCOUNT_ID_JSON KEYSIZE WELLKNOWN PRIVATE_KEY_RENEW OPENSSL_CNF CONTACT_EMAIL LOCKFILE } # Main method (parses script arguments and calls command_* methods) main() { exit_with_errorcode=0 skip_exit_hook=no COMMAND="" set_command() { [[ -z "${COMMAND}" ]] || _exiterr "Only one command can be executed at a time. See help (-h) for more information." COMMAND="${1}" } check_parameters() { if [[ -z "${1:-}" ]]; then echo "The specified command requires additional parameters. See help:" >&2 echo >&2 command_help >&2 exit 1 elif [[ "${1:0:1}" = "-" ]]; then _exiterr "Invalid argument: ${1}" fi } [[ -z "${*}" ]] && eval set -- "--help" while (( ${#} )); do case "${1}" in --help|-h) command_help exit 0 ;; --env|-e) set_command env ;; --cron|-c) set_command sign_domains ;; --register) set_command register ;; --account) set_command account ;; # PARAM_Usage: --accept-terms # PARAM_Description: Accept CAs terms of service --accept-terms) PARAM_ACCEPT_TERMS="yes" ;; --display-terms) set_command terms ;; --signcsr|-s) shift 1 set_command sign_csr check_parameters "${1:-}" PARAM_CSR="${1}" ;; --revoke|-r) shift 1 set_command revoke check_parameters "${1:-}" PARAM_REVOKECERT="${1}" ;; --deactivate) set_command deactivate ;; --version|-v) set_command version ;; --cleanup|-gc) set_command cleanup ;; --cleanup-delete|-gcd) set_command cleanupdelete PARAM_CLEANUPDELETE="yes" ;; # PARAM_Usage: --full-chain (-fc) # PARAM_Description: Print full chain when using --signcsr --full-chain|-fc) PARAM_FULL_CHAIN="1" ;; # PARAM_Usage: --ipv4 (-4) # PARAM_Description: Resolve names to IPv4 addresses only --ipv4|-4) PARAM_IP_VERSION="4" ;; # PARAM_Usage: --ipv6 (-6) # PARAM_Description: Resolve names to IPv6 addresses only --ipv6|-6) PARAM_IP_VERSION="6" ;; # PARAM_Usage: --domain (-d) domain.tld # PARAM_Description: Use specified domain name(s) instead of domains.txt entry (one certificate!) --domain|-d) shift 1 check_parameters "${1:-}" if [[ -z "${PARAM_DOMAIN:-}" ]]; then PARAM_DOMAIN="${1}" else PARAM_DOMAIN="${PARAM_DOMAIN} ${1}" fi ;; # PARAM_Usage: --ca url/preset # PARAM_Description: Use specified CA URL or preset --ca) shift 1 check_parameters "${1:-}" [[ -n "${PARAM_CA:-}" ]] && _exiterr "CA can only be specified once!" PARAM_CA="${1}" ;; # PARAM_Usage: --alias certalias # PARAM_Description: Use specified name for certificate directory (and per-certificate config) instead of the primary domain (only used if --domain is specified) --alias) shift 1 check_parameters "${1:-}" [[ -n "${PARAM_ALIAS:-}" ]] && _exiterr "Alias can only be specified once!" PARAM_ALIAS="${1}" ;; # PARAM_Usage: --keep-going (-g) # PARAM_Description: Keep going after encountering an error while creating/renewing multiple certificates in cron mode --keep-going|-g) PARAM_KEEP_GOING="yes" ;; # PARAM_Usage: --force (-x) # PARAM_Description: Force certificate renewal even if it is not due to expire within RENEW_DAYS --force|-x) PARAM_FORCE="yes" ;; # PARAM_Usage: --force-validation # PARAM_Description: Force revalidation of domain names (used in combination with --force) --force-validation) PARAM_FORCE_VALIDATION="yes" ;; # PARAM_Usage: --no-lock (-n) # PARAM_Description: Don't use lockfile (potentially dangerous!) --no-lock|-n) PARAM_NO_LOCK="yes" ;; # PARAM_Usage: --lock-suffix example.com # PARAM_Description: Suffix lockfile name with a string (useful for with -d) --lock-suffix) shift 1 check_parameters "${1:-}" PARAM_LOCKFILE_SUFFIX="${1}" ;; # PARAM_Usage: --ocsp # PARAM_Description: Sets option in CSR indicating OCSP stapling to be mandatory --ocsp) PARAM_OCSP_MUST_STAPLE="yes" ;; # PARAM_Usage: --privkey (-p) path/to/key.pem # PARAM_Description: Use specified private key instead of account key (useful for revocation) --privkey|-p) shift 1 check_parameters "${1:-}" PARAM_ACCOUNT_KEY="${1}" ;; # PARAM_Usage: --domains-txt path/to/domains.txt # PARAM_Description: Use specified domains.txt instead of default/configured one --domains-txt) shift 1 check_parameters "${1:-}" PARAM_DOMAINS_TXT="${1}" ;; # PARAM_Usage: --config (-f) path/to/config # PARAM_Description: Use specified config file --config|-f) shift 1 check_parameters "${1:-}" CONFIG="${1}" ;; # PARAM_Usage: --hook (-k) path/to/hook.sh # PARAM_Description: Use specified script for hooks --hook|-k) shift 1 check_parameters "${1:-}" PARAM_HOOK="${1}" ;; # PARAM_Usage: --preferred-chain issuer-cn # PARAM_Description: Use alternative certificate chain identified by issuer CN --preferred-chain) shift 1 check_parameters "${1:-}" PARAM_PREFERRED_CHAIN="${1}" ;; # PARAM_Usage: --out (-o) certs/directory # PARAM_Description: Output certificates into the specified directory --out|-o) shift 1 check_parameters "${1:-}" PARAM_CERTDIR="${1}" ;; # PARAM_Usage: --alpn alpn-certs/directory # PARAM_Description: Output alpn verification certificates into the specified directory --alpn) shift 1 check_parameters "${1:-}" PARAM_ALPNCERTDIR="${1}" ;; # PARAM_Usage: --challenge (-t) http-01|dns-01|tls-alpn-01 # PARAM_Description: Which challenge should be used? Currently http-01, dns-01, and tls-alpn-01 are supported --challenge|-t) shift 1 check_parameters "${1:-}" PARAM_CHALLENGETYPE="${1}" ;; # PARAM_Usage: --algo (-a) rsa|prime256v1|secp384r1 # PARAM_Description: Which public key algorithm should be used? Supported: rsa, prime256v1 and secp384r1 --algo|-a) shift 1 check_parameters "${1:-}" PARAM_KEY_ALGO="${1}" ;; # PARAM_Usage: --acme-profile profile_name # PARAM_Description: Use specified ACME profile --acme-profile) shift 1 check_parameters "${1:-}" PARAM_ACME_PROFILE="${1}" ;; # PARAM_Usage: --order-timeout seconds # PARAM_Description: Amount of seconds to wait for processing of order until erroring out --order-timeout) shift 1 check_parameters "${1:-}" PARAM_ORDER_TIMEOUT=${1} ;; *) echo "Unknown parameter detected: ${1}" >&2 echo >&2 command_help >&2 exit 1 ;; esac shift 1 done case "${COMMAND}" in env) command_env;; sign_domains) command_sign_domains;; register) command_register;; account) command_account;; sign_csr) command_sign_csr "${PARAM_CSR}";; revoke) command_revoke "${PARAM_REVOKECERT}";; deactivate) command_deactivate;; cleanup) command_cleanup;; terms) command_terms;; cleanupdelete) command_cleanupdelete;; version) command_version;; *) command_help; exit 1;; esac exit "${exit_with_errorcode}" } # Determine OS type OSTYPE="$(uname)" if [[ ! "${DEHYDRATED_NOOP:-}" = "NOOP" ]]; then # Run script main "${@:-}" fi # vi: expandtab sw=2 ts=2 dehydrated-0.7.2/README.md0000644000175000017500000001337315012216410014755 0ustar lukas2511lukas2511# dehydrated [![Donate](https://img.shields.io/badge/Donate-PayPal-green.svg)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=23P9DSJBTY7C8) ![](docs/logo.png) Dehydrated is a client for signing certificates with an ACME-server (e.g. Let's Encrypt) implemented as a relatively simple (zsh-compatible) bash-script. This client supports both ACME v1 and the new ACME v2 including support for wildcard certificates! It uses the `openssl` utility for everything related to actually handling keys and certificates, so you need to have that installed. Other dependencies are: cURL, sed, grep, awk, mktemp (all found pre-installed on almost any system, cURL being the only exception). Current features: - Signing of a list of domains (including wildcard domains!) - Signing of a custom CSR (either standalone or completely automated using hooks!) - Renewal if a certificate is about to expire or defined set of domains changed - Certificate revocation - and lots more.. Please keep in mind that this software, the ACME-protocol and all supported CA servers out there are relatively young and there might be a few issues. Feel free to report any issues you find with this script or contribute by submitting a pull request, but please check for duplicates first (feel free to comment on those to get things rolling). ## Getting started For getting started I recommend taking a look at [docs/domains_txt.md](docs/domains_txt.md), [docs/wellknown.md](docs/wellknown.md) and the [Usage](#usage) section on this page (you'll probably only need the `-c` option). Generally you want to set up your WELLKNOWN path first, and then fill in domains.txt. **Please note that you should use the staging URL when experimenting with this script to not hit Let's Encrypt's rate limits.** See [docs/staging.md](docs/staging.md). If you have any problems take a look at our [Troubleshooting](docs/troubleshooting.md) guide. ## Config dehydrated is looking for a config file in a few different places, it will use the first one it can find in this order: - `/etc/dehydrated/config` - `/usr/local/etc/dehydrated/config` - The current working directory of your shell - The directory from which dehydrated was run Have a look at [docs/examples/config](docs/examples/config) to get started, copy it to e.g. `/etc/dehydrated/config` and edit it to fit your needs. ## Usage: ```text Usage: ./dehydrated [-h] [command [argument]] [parameter [argument]] [parameter [argument]] ... Default command: help Commands: --version (-v) Print version information --display-terms Display current terms of service --register Register account key --account Update account contact information --cron (-c) Sign/renew non-existent/changed/expiring certificates. --signcsr (-s) path/to/csr.pem Sign a given CSR, output CRT on stdout (advanced usage) --revoke (-r) path/to/cert.pem Revoke specified certificate --deactivate Deactivate account --cleanup (-gc) Move unused certificate files to archive directory --cleanup-delete (-gcd) Deletes (!) unused certificate files --help (-h) Show help text --env (-e) Output configuration variables for use in other scripts Parameters: --accept-terms Accept CAs terms of service --full-chain (-fc) Print full chain when using --signcsr --ipv4 (-4) Resolve names to IPv4 addresses only --ipv6 (-6) Resolve names to IPv6 addresses only --domain (-d) domain.tld Use specified domain name(s) instead of domains.txt entry (one certificate!) --ca url/preset Use specified CA URL or preset --alias certalias Use specified name for certificate directory (and per-certificate config) instead of the primary domain (only used if --domain is specified) --keep-going (-g) Keep going after encountering an error while creating/renewing multiple certificates in cron mode --force (-x) Force certificate renewal even if it is not due to expire within RENEW_DAYS --force-validation Force revalidation of domain names (used in combination with --force) --no-lock (-n) Don't use lockfile (potentially dangerous!) --lock-suffix example.com Suffix lockfile name with a string (useful for with -d) --ocsp Sets option in CSR indicating OCSP stapling to be mandatory --privkey (-p) path/to/key.pem Use specified private key instead of account key (useful for revocation) --domains-txt path/to/domains.txt Use specified domains.txt instead of default/configured one --config (-f) path/to/config Use specified config file --hook (-k) path/to/hook.sh Use specified script for hooks --preferred-chain issuer-cn Use alternative certificate chain identified by issuer CN --out (-o) certs/directory Output certificates into the specified directory --alpn alpn-certs/directory Output alpn verification certificates into the specified directory --challenge (-t) http-01|dns-01|tls-alpn-01 Which challenge should be used? Currently http-01, dns-01, and tls-alpn-01 are supported --algo (-a) rsa|prime256v1|secp384r1 Which public key algorithm should be used? Supported: rsa, prime256v1 and secp384r1 --acme-profile profile_name Use specified ACME profile --order-timeout seconds Amount of seconds to wait for processing of order until erroring out ``` ## Chat Dehydrated has an official IRC-channel `#dehydrated` on libera.chat that can be used for general discussion and suggestions. The channel can also be accessed with Matrix using the official libera.chat bridge at `#dehydrated:libera.chat`. dehydrated-0.7.2/CHANGELOG0000644000175000017500000001600115012216410014677 0ustar lukas2511lukas2511# Change Log This file contains a log of major changes in dehydrated ## [0.7.2] - 2025-05-18 ## Added - Implemented support for certificate profile selection - Added a configuration parameter to allow for timeouts during order processing (`ORDER_TIMEOUT`, defaults to 0 = no timeout) - Allowed for automatic deletion of old files (`AUTO_CLEANUP_DELETE`, disabled by default) ## Changed - Renew certificates with 32 days remaining (instead of 30) to avoid issues with monthly cronjobs (`RENEW_DAYS=32`) ## Fixed - Changed behaviour of `openssl req` stdin handling to fix compatibility with OpenSSL version 3.2+ ## [0.7.1] - 2022-10-31 ## Changed - `--force` no longer forces domain name revalidation by default, a new argument `--force-validation` has been added for that - Added support for EC secp521r1 algorithm (works with e.g. zerossl) - `EC PARAMETERS` are no longer written to privkey.pem (didn't seem necessary and was causing issues with various software) ## Fixed - Requests resulting in `badNonce` errors are now automatically retried (fixes operation with LE staging servers) - Deprecated `egrep` usage has been removed ## Added - Implemented EC for account keys - Domain list now also read from domains.txt.d subdirectory (behaviour might change, see docs) - Implemented RFC 8738 (validating/signing certificates for IP addresses instead of domain names) support (this will not work with most public CAs, if any!) ## [0.7.0] - 2020-12-10 ## Added - Support for external account bindings - Special support for ZeroSSL - Support presets for some CAs instead of requiring URLs - Allow requesting preferred chain (`--preferred-chain`) - Added method to show CAs current terms of service (`--display-terms`) - Allow setting path to domains.txt using cli arguments (`--domains-txt`) - Added new cli command `--cleanupdelete` which deletes old files instead of archiving them ## Fixed - No more silent failures on broken hook-scripts - Better error-handling with KEEP_GOING enabled - Check actual order status instead of assuming it's valid - Don't include keyAuthorization in challenge validation (RFC compliance) ## Changed - Using EC secp384r1 as default certificate type - Use JSON.sh to parse JSON - Use account URL instead of account ID (RFC compliance) - Dehydrated now has a new home: https://github.com/dehydrated-io/dehydrated - Added `OCSP_FETCH` and `OCSP_DAYS` to per-certificate configurable options - Cleanup now also removes dangling symlinks ## [0.6.5] - 2019-06-26 ## Fixed - Fixed broken APIv1 compatibility from last update ## [0.6.4] - 2019-06-25 ## Changed - Fetch account ID from Location header instead of account json ## [0.6.3] - 2019-06-25 ## Changed - OCSP refresh interval is now configurable - Implemented POST-as-GET - Call exit_hook on errors (with error-message as first parameter) ## Added - Initial support for tls-alpn-01 validation - New hook: sync_cert (for syncing certificate files to disk, see example hook description) ## Fixes - Fetch account information after registration to avoid missing account id ## [0.6.2] - 2018-04-25 ## Added - New deploy_ocsp hook - Allow account registration with custom key ## Changed - Don't walk certificate chain for ACMEv2 (certificate contains chain by default) - Improved documentation on wildcards ## Fixes - Added workaround for compatibility with filesystem ACLs - Close unwanted external file-descriptors - Fixed JSON parsing on force-renewal - Fixed cleanup of challenge files/dns-entries on validation errors - A few more minor fixes ## [0.6.1] - 2018-03-13 ## Changed - Use new ACME v2 endpoint by default ## [0.6.0] - 2018-03-11 ## Changed - Challenge validation loop has been modified to loop over authorization identifiers instead of altnames (ACMEv2 + wildcard support) - Removed LICENSE parameter from config (terms of service is now acquired directly from the CA directory) ## Added - Support for ACME v02 (including wildcard certificates!) - New hook: generate_csr (see example hook script for more information) - Calling random hook on startup to make it clear to hook script authors that unknown hooks should just be ignored... ## [0.5.0] - 2018-01-13 ## Changed - Certificate chain is now cached (CHAINCACHE) - OpenSSL binary path is now configurable (OPENSSL) - Cleanup now also moves revoked certificates ## Added - New feature for updating contact information (--account) - Allow automatic cleanup on exit (AUTO_CLEANUP) - Initial support for fetching OCSP status to be used for OCSP stapling (OCSP_FETCH) - Certificates can now have aliases to create multiple certificates with identical set of domains (see --alias and domains.txt documentation) - Allow dehydrated to run as specified user (/group) ## [0.4.0] - 2017-02-05 ## Changed - dehydrated now asks you to read and accept the CAs terms of service before creating an account - Skip challenges for already validated domains - Removed need for some special commands (BusyBox compatibility) - Exported a few more variables for use in hook-scripts - fullchain.pem now actually contains the full chain instead of just the certificate with an intermediate cert ## Added - Added private-key rollover functionality - Added `--lock-suffix` option for allowing parallel execution - Added `invalid_challenge` hook - Added `request_failure` hook - Added `exit_hook` hook - Added standalone `register` command ## [0.3.1] - 2016-09-13 ## Changed - Renamed project to `dehydrated`. - Default WELLKNOWN location is now `/var/www/dehydrated` - Config location is renamed to `dehydrated` (e.g. `/etc/dehydrated`) ## [0.3.0] - 2016-09-07 ## Changed - Config is now named `config` instead of `config.sh`! - Location of domains.txt is now configurable via DOMAINS_TXT config variable - Location of certs directory is now configurable via CERTDIR config variable - signcsr command now also outputs chain certificate if --full-chain/-fc is set - Location of account-key(s) changed - Default WELLKNOWN location is now `/var/www/letsencrypt` - New version of Let's Encrypt Subscriber Agreement ## Added - Added option to add CSR-flag indicating OCSP stapling to be mandatory - Initial support for configuration on per-certificate base - Support for per-CA account keys and custom config for output cert directory, license, etc. - Added option to select IP version of name to address resolution - Added option to run letsencrypt.sh without locks ## Fixed - letsencrypt.sh no longer stores account keys from invalid registrations ## [0.2.0] - 2016-05-22 ### Changed - PRIVATE_KEY config parameter has been renamed to ACCOUNT_KEY to avoid confusion with certificate keys - deploy_cert hook now also has the certificates timestamp as standalone parameter - Temporary files are now identifiable (template: letsencrypt.sh-XXXXXX) - Private keys are now regenerated by default ### Added - Added documentation to repository ### Fixed - Fixed bug with uppercase names in domains.txt (script now converts everything to lowercase) - mktemp no longer uses the deprecated `-t` parameter. - Compatibility with "pretty" json ## [0.1.0] - 2016-03-25 ### Changed - This is the first numbered version of letsencrypt.sh dehydrated-0.7.2/LICENSE0000644000175000017500000000207515012216410014500 0ustar lukas2511lukas2511The MIT License (MIT) Copyright (c) 2015-2021 Lukas Schauer Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. dehydrated-0.7.2/.gitignore0000644000175000017500000000014215012216410015454 0ustar lukas2511lukas2511private_key.pem private_key.json domains.txt config hook.sh certs/* archive/* accounts/* chains/* dehydrated-0.7.2/docs/0000755000175000017500000000000015012216410014417 5ustar lukas2511lukas2511dehydrated-0.7.2/docs/logo.png0000644000175000017500000022530215012216410016071 0ustar lukas2511lukas2511‰PNG  IHDR\r¨f}iCCPicc(‘}‘=HÃP…OS¥E+‚vqÈÐ:YqÔ*¡B¨Zu0yé4iHR\ׂƒ?‹Ug]\AðÄÍÍIÑEJ¼/)´ˆõÂã}œwÏá½û¡^fšÕ5hºm¦q1“]¯è…"*3˘“¤$:Ö×=õRÝÅxVç¾?«OÍY ð‰Ä³Ì0mâ âéMÛà¼OfEY%>'3é‚Ä\W<~ã\pYà™a3š'‹…6VÚ˜MxŠ8¢j:å UÎ[œµr•5ïÉ_Êé+Ë\§5‚± "TQB6b´ë¤XHÑy¼ƒØõKäRÈU#Ç*Ð »~ð?ø=[+?9á%…â@÷‹ã|DÀ.Ш9Î÷±ã4Nÿ3p¥·ü•:0óIz­¥EŽ€þmà⺥){Àå0ôdȦìJ~ZB>¼ŸÑ7eÁ[ gÍ›[ó§@šf•¼Ñe¯wxw°}nÿö4ç÷'ûr‰›Üu. cHRMz&€„ú€èu0ê`:˜pœºQ<bKGDÿÿÿ ½§“ pHYsooÞ©±tIMEå  ö&N÷ÃzTXtRaw profile type icc8¥S[Žä üç{ãg8N‚´÷¿À ýšž•f×J¥ å²)ÒïZÓ¯j˜`ž UI›0MH›^Ɔ‚lŒrH‘ìOßüÍþ8‡HÒ¬ddÀY@€+üCt¯š‚9¢¶»²Fúáú¦¬b¤Qhv6‚!yc`h ] R3Ÿl¼gv½‡cáçÖäãœcŒD{lxÁ¯zÇí ß‚Dì'R1óª¾á3þÍú4¤j‹Ú öY¸V ®îçÛn“ÃÕDßOiŸdUÑKDhoXi'ð]„ŒSêpšM=¡¾h Ûú¿bͰ„à"Àwéï òùP0 =‹a›E(Š0;‘öq!üÍð¸O1 ovO)á³!3TŽÒ®>‚Pæ¿•©°·¿y~ÛªD­N¢ê*>)ÐÑуÊG¥È¥‡¢~ÌÓ¸iå„Xlæí˜ß|44»mîm¢=yTÉ‹§Å¤Ì6ƒSn/¾‹ M'µ¹Q8 Ƶ΅¥”sUâ.õ;ßía¿;ì;÷ÅxÿAôjÄô«; ß&2B5zTXtRaw profile type xmpX…ÕYÛ’«8 |÷Wì'Ø’mÁço[µûùÛ- ™$ç̰U‡„B°.-©%3Iø÷ïÂ_x©ª½êlÅšªÖK-–%V©¥Zí뤣È4_.—Y÷ûšy§˜–‘¢3ºf©ü,9ðŠ@ćŰÝÕˆÏI ‚b+ÚçÞí§û-ÅÈa¦ ´IY¢º_©ø;L9!*#*¡Y*1‡#¢q´XÓæ Þùâh¬&wŽ3e€p$Bà8:ÂF|ƒèÛô„ÔuËWØ'l‡+/™ÊJ\ÆQB„Ž}Ϭàªð®zM¹ÀeXNJ¥ÄÌ!¥»FñÖ09ˆ !µVeA ÙÀ>LÛTV®9•˜{*À" ñD¦Dð-q°->(ž¯ŒÝ×=UÉîŸÛ³¸øUK@ü=G¨×¥WŽJdæ©)ÌÓ6%W¤ X§µÿµÛnõñRúN9=VÒ¸™¸  ‹9׫‡¹à¸z]'ºÏô=)¸A:;aÄúMìnk-í×ö«HtßVÂÚïÛæÎ û`•ä³3ï'ĽV€šbïfÇE}O™_+@þ¡ËÖîì²Ö?•uD™@ÊÑë™ýŠÝã Æ™à÷’bïï}‹S¨f°¹Vy‡†[EÅ•‚E™²³ÚwÚV¡e*oŠÒíèÙ¿5nHM 7b9) 9ù9j*Ùwý†9VÚ…÷9êH™÷µÄƒ–̯Jík|ùJ—f!¼{T¸=Úì\ùÃM{ø¢“íñ+´ª{ÂýäÇÔÿWC-rD =+Ìdžök–}Phßm™S‡v¬ÿÏc9¢†¾æO#:`èW,û¨Ð¾×2'íHÿŸ-´8œ ÑC…ùóˆzϲ í;-súÐ~Þÿ' íè?3g í>–3 :`è]a>.´ßgÙ„öÓ– üNáɯG¥Ní»$³öÃOø“ëpi SºvorNTÏ¢wš€IDATxÚì½wœdéUßý}n®\ÓäœóìÎìÎî*­r@"²MÆð:¾Æ6¼6lÀ›l› $!! ¡°³yrÎ9u®nxž÷{«ºº§»'õ̬vç|>µÓÛ]uëÞçÞsž~çwäµ*âWûÉ7¿Üê!{$Gîä¾<2ä®å‘xm‰¸Åÿ7DÝâÿÉ#¹-yd^;"¦üÜújˆbBÙ[nýû#y$·- ÀkCDË¿—èÑ¿ 2z)‚GròÈ<\™n××0+úWgBù]À|&xdÉÊ#ððdªò7v|°xôŠ¿T£—Ë#CðHîAô‡}o@ië§*¾$€ Ð ôlÐõ?½· ÕvRJEhLB£05G0õ{¦ûÎGòHšòÈ2xd”LÍò7}6¡ËŸº€¾EB¬þ™¶Ì·½çϼ{Ù¿øáTòñMáq›×йcSbq"¶jÓx~U{©â\ò·ï‘!x$w, Àý—©Êßêò'vB—Þ› }ûOÎïûÈ®ïý¶íó~âcº=¯¤œt0£=KúñM¢{óšìr!ÖnË/±*5í¼”¾•Ö°`:%dIS€û+3)Œ0ÞïzMXø½ŽýÖmXõöût}ç{Ñâ1PÓ$ï•!°ú»É<¹Më[½¬këmX?žŸçÖëò‚T“ ÁtÞÀÔóƒG†à ) Àý“©1¿Áäx¿ èëbùOgRü–gŸ|ïŠñ#éÔ“[oïèJ! gÉ|²Om3æ-èXWªlX™+t»žwC)}gkX…¤E€û#Ó•ùZK|Ý@ÿ&]Ûð3=]yóGÞÿ䂟ù~ÓY4ï&—ÿ–¢šm_½ŒŽ][ííK6å kçJéë¾ïæÂshätåI‹<2s/Ó)#Ù×,ñ½Ã0vüØÒßóØ?û'«{¾ïÛ…žI‚¼¼ŽRhɉÍkéÚ±9±8î¬Ú<ž_Ù^ª8ýÀ­Nx!…¤) ÀÜÊtnÿTåŸÿQÛzË÷nZý]ëáûÛÞ÷V„aLïߥYÒ;6‰žMk²+k7Žç›•š>C¢°õ¼§»–ÆÏ ÁëP€¹“[íü½,ü±¸ó®ïصí[Wþ«˦vnžSÅoJ#Q8ÐCf×6­oÕÒî5®»~Ýx~À­¹ò¼R>@¢Ö°`¶ë‚G†àu' ÀÜI«KÝHø5•߆…?•Œ¿ÿÏîzÿÒù£ñøš¥¨;÷ïTZ…mOm7ç-è›·¾TY¿"Wìʹž?C¢p¶àV¿$ßdòÈÌ4bj¯¡ü‹~:•ø–÷½ûÍïYüÿü°m/ì¿ÿÊß*J¡96ñ5ËißµÍYØ‘]²1WX3P(§Z…Óa`æªÁ#oàu À½K«‚´Öù³@ ~2ÿ{ßõ¦÷,ú?lYýÝVù[E)ôd‚ä–ut=¾1¹ÄqVmϯȔÊöÅ ðª“+€DoydîM¦Kú9„uþn`ÞÄì·ë[žøà’ù#ŽÕßóð”ŠmQ¢puÛ X»q,¿X¯Ö´sRzÁ„705?0›1øf1³£ÞpòÈÜ›Lçú' ~}ï1'þÉÖõßµü_ýhÊYºà5£üÀ¤DaöÉmzߊÅÝkjîúuãùþJÝ .©i…·Zxí)ÖLôj·úÛB€»—©»«ëß»\«lñü®ÿç?8|l*x )«(…0uœe ÃDá@Ïüõ…ÒúeùbǨçyC Å' ¶BÝ­R¿áŒÁ#pwÒú€´ÖûS@—€ù?‘Ih×?ýÖÇ:¿ý]ß| ÷?t?¥k›¿õñMïŸÿCq„ã<ìs½7Q ¡iØ úÈîÚ¦,]Ø»¦RÛ°&—ï)º®eHt§‰Â;•»QúÆy5HVm ýlq³1˜ÉÜêü¿iÁ#pç2û'rÿ?šJ¼óñïùÀÆô“ÛîÌ÷aˆRË$¶bí»¶Yó{»nÈ×-ʳƒžïކ>ÃÔ°à^e.”>Fè™% t–°D›Š~×0&“;&g2¯;ïà‘¸3™šýnEýµwÁâ.øÀ²O|W‡ÙÝñú1 Q -#¾q5;7Ç¥+6çWu˱KAà–gO¶.Æí(ÏlŸêÞ7¸A§ž Tô,!ãR'ЇþÍB,O zÇ!¡BC`‚~½ÕLn¡~]z ÀÉtñ#ûßù˜¦mxÏŽMÏö~ç{-ašû\ïŸ(…Þ–!µ}#Ý[×e–kbíÆÑü³RÓÎIéúk¡â·N1j]ËÛÙ-§*}+“òÔ¾¡ô„„+=ÌÛ(ĚŜg¾·¯ëX¿ò[ÞÜÓùÔR®^È~KÊtI©xmbC«1°˜l¦Nkú¦÷Œ{?ÄRZʆ[c™ ³«—%´Düõ·ûO)A@|ýJ®X¤w¼íÉÕKþüs‹v½¸ËߌæþákAp8Z—VCИnÔøÿ™dª2µ®uÃÕoÌQp6Näî]+ÄÀã¶µrc{fí⥠wo[ß–Ù¹YÄV-EÕ]VŸ<7°óàñc‡N¯Ÿ»tãÜÐè¹#åꙃ¾é 'ÂÒ:ˆ¥½¼èf‘Ü<®M0yŽãl×y«õ¸oòÈܾÌTêjR}-LÄæ%V,BÓPApgGÿ&%%Â4I¿yñÍkbÝ_z~çÊ¿ø»•o:tâëÿ·TùüQ)*I룩 2¤¸u×oí-&vý†»ŸÒ »Jˆþ–¹rS[fÍ’%ó—ôl[ߞݱY‹­[ŽÑцÐõ)ÀšßGú-;E_±”^vñZzË‘S+žÝÌ>qvìò•ë—OäŠgÖݳǤ¼6c@‰É¡F8™©1ª­afšÝتàÓ)ûÔçë„ׄòM"ÓÅÿìÿ@Öüæ’ù?ûæOþâÚø¦Õ¯]äß}]!‚ú•ëŒþÕ9÷™¯œüÌ…+¿ýÇ•êW H¸ƒܬü3%õ¦ÛéÑ+»Rˆ¾ÇLcÅ–lzíÒÅó—önYÛ‘}b‹_·£»cBégòÈ¢sFdÝÅ£vêÅÇÕØá“¥ëg.Þ8;z‡<ÿâ9ò¡”¹{ïní´Ê}3<€»—V·Ôì„T¶-Ûft¶£^ïîÿL¢J)¬y}ôÿÄÇ0Û3+ßòkðî?«T_t''ÓŽ[×q:¥ŸºÓ'ÌRèÝa™Ë7gRk—-XÖ»emgÛŽÍF|ã*ÌžN„a4•þ–žXtÎÂÐ1º±æõ’zÓÑ[,§–^º–ÚräÔò·<þ–ágÇ.]¼vùØXþÌ!Ï;{BÊ«£ßäÞÁ#pw2µØè"ìÈ&ôTü› ù7ç+# (¼¸Ÿë_øÆØþRå%7Ü n+ Ysý¸9©7IéAïc¦±tk:¹vù‚å}›Vw·=±ÅHl\Ù×…0ÍÛWúÙD*T”¦ÐR âVß°’Ì;Ÿ6ÛŸìé~a_φby믕‡Î\¼qvhôÜÑråÌAÏ¿xçÀ;x ¹ƒGàî¥Õ0Ú5‘rÚ3¶pì×p¦Ñ4¼ÑqFþüsÿ‹ÏŸù› W>õ)×ÛÍ„ÛßÈ™À„â·îô1&”>==ÆÒ­©ÄÚóûVôoZÝÓ¾s³™Ø¼³¿ͲPJ†J;‡9¡ ”‚ R£68LõÆ0AÝÃîÈ2ðÆÊ¤„¬T“î¥ëË69µìÙÇÞ<|ìÌØå+×/Ÿ/œ9XwÏ{ïྃGàÞ¤¹ƒµkZÒn˘Âx.©ÿS>pœK¿ý'µçŸ{å…?Î?Jª“@Žð¡‡ ×¾žt˜Ó§û¡{›¡/ÙžJ¬]1лr`ýÊžö'¶Ø‰-k±æõ¡ÙVè²K9çJTëÔ†F©\"¨Ö°²)’K`wdÑL3üîˆ9¶v9±u+hÿÐÛÍùÃã=kNŸïyâÀñ­ã‡N”¯Ÿ½xãÜàèù#åÊéC¾ñŒbп½ÊÂÔqïw’;³ümZy>­s&“ÊSY]Oš™”&4íµÕ÷ßWA <ŸÜß}•ãÿãÏÿêÄÙ/|ªî¾èÃu`”ða‡ ŽÅä²]º º¶éúâÇ“ñµ«zVõ¯_ÙÛ±s³“ܲkAšãÜ¥o$ƒzúð8•kƒø¥ f*AbA?vgzÃà(5ù¾¶æt³¯ k ‡ÔÓ‰ÞR%¹ôÒµe›œ\ö¶Çß4|üÌøåË×/ݦwPgÂ;hƒ;õnÛ<2w.S« [ºæè‰˜@¼ +šFP(1ô‡Éžÿó·'~ÿúÐç^ ‚£„ÿáìîîL~He¡s«®-z,[³¶¯{õ¼µË:vnŽ¥¶oÄ^4€»¿J/]úè8•«ƒx…"F"N¬¿§«ݱ@šªô³IKîAKÆ›ÞALJÞiÎë^súB÷Žm;t¢|ýÌÅç‡FÏ.WNðü‹çÃÊBž0wP%45&‡ ­¥ÔFeªAn[Íj€»“©ˆ.ÍÔ4[wì‡}^n4 ÷Æ0W~ワþí—÷|r¼ð…+J] ÜÙÊ„^Œç‰´mÒ´OÄì5ëº;×.\»|~çŽM‰Ôã±—,@OÄBÅ»_Jïù¸c9*׆psytÇ&ÖÛMfÍ2ô˜ƒ ¤BÝkg‹w€®MxÏl½ÅrrÙ¥ë˶9¹ìmû¿yøä¹±K¯\>6V8}ÄóΕòêØÜxpsRñ&ydî] ,!4a¼1ÕBÓ¨»Ä…_ù]÷³ÿðüîß.U¾Z]þ2áƒØ€æ::¤Ö Ñÿ¤c­ÞØÑ¾vñÊÅ‹ºwnÎdvlÆY±=•@)9·Ø‰†Òû^.OåÚ õÑq4Ó$Ö×Ejù"ŒD !ÄDõà~ån'y bëW[·‚ô³»ÌÌ¡=]/ìÙ\©m­]¾Vº~æâ3C£g—«§ú!î@Í\YhM&L6­ÂŒFà‘˜#Ñ‫šFåðINý¿Sùóö=÷¿kõ—üÐÝhi¹],èÜišË¶gÓë–/[¸´÷±íÙ'·j±5ËÑÛÓ‘âÝ‹}['*½ Ü|‘êµAê#ãMÃéé cëzŒdœfž¦u§~릔Â/W©Ý+ Ê÷±»:˜ÿCÁL%„,URK/]Mm>trùÛ{Ëà±3£—®Ü¸x,_<} îž=¦ÔµŒs{ÞÁÔp¦1 À½‹dMI_ùþÃ>—û+šFyïŽýÇO–ïáç?íz‡ 8È´A|»®Ï{2•X½váÀÊ­k»Ûwm7›×LFåI…š+°„!B…ö %ª×‡¨ Ž §«ƒ¶«1Óɦr'qý½žZTYð«ujƒ#T¯ÔêXmÒ+cµgÐ c¢²J_·’øúUt|Û»¬ƒÃ}ëNœëÛµÿèc£‡N®ž»tíôÈø™C•ÚéƒApé ¢+ËLöùƒFu¡áÀ#ðÈÜ´ÆZž”nP­?ìóº¢i”_ÚÏÁÿðÉÊo:¾ï˞РH¯ÑÄÀ3޳xk_×òEVt=µÍI=¾ s~oX«Ÿ €Îi ÕCÔnŒ ‚»³ìúX™Thp°ÒOT\êÃc•…t’äÂyØmh–yëÊ‚¡cÎïÃZ0@ú­Oh}ùbvù¹ËÙm¯yçþcµÁ“ç†Î]¹ßóÏžVê†zB£Ð0 &<ƒ›ŒÀ#pçÒˆ©?K ¨ùAÙ/”¤RR»ûC¿FEÓ(¿|€½¿ø[µÿrèÄÉ—ƒ`¼2O˜Æü§Û2ó׬Z:пkk:óÔvœå‹Ñ“±0žžóZ}¨ôA¥í¨Ã®‹Ýž%½j VÛäõA+½ô<ê£9ª×qsEŒDŒX_7N÷]VZP‰z6MbÛ:ÛÖÑU©9‹® .XøÔ‚7í=¼kèÈ©±Ë—®]81^8¶¯î?,å¹ 0dPL ‰þÿ‘¸i*?à²Pð íud4Ê«‡xõÓûo‡N ŽH©}"[õä‚þŽåoÊv¾y‡ß¼£³-DÐɹM浺ÑõI4É¥7t¸Òû^.GåÚ îhÍ2‰õw“^µtî+ AäØöÒ…8ËÑöÞ7ÃcÝ«OïÞ¹ÿØö÷í=’»qìÌù#¥ò×~£XþbM©FßÅÔ—Ô#p÷ÒjüA0V¹1R“µºùzášFåÐ üûß’_>rº¶+Ì>µnEß·=ieŸy kñü™×tñç*®ÜèZúÈ•kC·й¯ Ò’oȨ^¢6<ŠÐuœžNÚ·ÍÇH&LeAJ”ÓÀêïÁH%ÑcŽðr…ìøé Ë-í‚Ó!È+ÕŠ2œÔwðÈÜ´fU}À½¤ÔXþÚPÎË¥¬dâ›Þ4J}§é“ :¯¾õéí‰%ßö.-õÄô®öpû˜Kÿ~tæh-”’a¾áz”oPQ’qÓš–$ラ,M²T¡væ"¹÷sùù½þ‘ãgJÇGs#u)ÇVÄc™_^8ðSW‹¥ç~~4÷»„ù€FR°Y|dî^ì6>P¿ ãׇF®ÕÏ]™o/œ÷Íݨ ¼á1.üÊï2vø>ô}ÁO~ ³§+Êâ˹¹¾Ù:}÷ s'§ÖÒT¦v}˜À ‚2k–cµ¥l’QÓµªÖ¨_¼Jáå\}n<~ø¤wbh´–÷<¯7æðô¢y=Ë7­^ÔõäVCUk”ÿë^!D_ÚL´d7呸=™®Àßp£< @áäxáÔνGK=óØ7/ @TÍåÚÿü3n|ãæ¿ëþÜ ·eæf·¿@'½|úƒèÜtj!”#¨¹Ô†F©^įְ2)’Ëb·gl¾AaÈáºx¯QÜ{˜ëßxUÜÌ?q}Я{AgÌÖ¶,èwVlZêÙ¹EKnY‹¹ =™`ü3_¡T÷ê„Êo0 á#po"‰< üb½~ü/íëé0z:¿9Ãc_ø:gþò ´¯ZÊüÿF[æÞöV€N®Hõz+@§ó¡tšù×¥>2NõÚ ^¡Œ™JŸß‡ÝÙþ`ó B„ëàûøW‡)8Êàs{Ô©WÇ/_÷Gk5™µ-±º¿Ç\±iÓ·kk¨ôó§4MùÞXŽœçU™y¦ã£$àmÈLóîZ“€u ¼WªË‡Ÿ9¼ð+/¼©ë»?ðÍÇ ¤ ¼Ë78ý‡IM*}üÛ±öß]V:€ÎÐ((…Óýp:^ˆ;–§rõîx=æïï&»vzÌ~pù††ÒÁð•#'~n:ûòAyâüeÿF¹"“–)–õvéïܰÊêßµMKm[¹ æ¦)¥ðÆ Œ{~£/c*óðȸ•ÜŽ+… ŸÎ_Yó©¿Ûܶ¾=¶jiÄžæAîlwu±‚Ñ/ïæâ‰³¬}ˤŸ~üŽãî™:íd×=|€Ž ÜñBGFz¬XŒ=¸|ƒQÙT!sªÇÎ0º{ç_Ú/Ož¹è_/”¤e,íîПzj»9ðäV-ýøF¬…hqç–MS*pGÇýqß/2¹? ùxdàöüÓ ƒh¼Õïå ¸þ©Ã'_MüÊï¾yñÏý€%ÛÒ¸å š¦a§S8é$ZûZ!òE®þ㋘–ɼ÷¿=•¸­¸¿ S™H˜µgH¯ZŠÕ–~HV/¤–í†FAœîNÚ·¬ Ëv“Bû»ÆB  (”¨Ÿ:ÏØ û¸øü^yêä9y5W McAW»¾mûcàÉ­Z&ê”Ô&uJÞzý”ïS÷Y`rOÀ#(0wÖµ#fyµ¨h0ÝèçzCý¯î]ùÿþ—>Ù–Bº.Ó¶t!‹ŸÞIߺ•hº>±3¡Ñi))¸§ 4AíÌE†Nœ£oÙB’Û6̪¨Ù¯Õ£„ÙA­†•I?œ„Ù¤sk4ÞT¨]o‚»«¶ «ÂÐãz!e»*µs—Ƚ´ŸËÏíQ§ŽVWFsRZÐÙ&ÞþÌcæü'¶hÙ›±–.@OÞe§¤ÈjÚX¾>.e‘Éüƒoxp';þÔ¡S'ÄÝo)ÂÙ€mómsþ{:võtõ([C¯U1"Aò ¿²—ë{²ømϰú=o¥88Ìø…+Ô‹E4Ó$ÕÛMçÒ…$»;ˆj S+”èÝþ,FgÛMIÌ}ö´…ýØ  C‹AªÖšü Þ€ ?/djÙî•C\yîUuæÐ uixTRÑמá™'¶è žØ"ÚvlÆ^±=•„fr—ç(@Vª”s…ÊpntÞÔøF2â6×øý$Úo&ïò­ÔÕqÂqT]i][ò¾ŽwoXܿ؎Ù7)¯Ð–ncJÉñOž‹/¼ µ*x^3ŽPŒLšùOlgÍ{ÞF¬-ý@êß²î‘;|ÓÔiÛ².Ü!ƒ`@§v@§§«ý¡t&SzQ¹:ˆ_®bf’$ÏÇñæ~È”²]aÏa®?÷ g÷S—n «º¨îlZìܾQ[°s“hß¹gåb´Lº%÷pïç(¹¥B©˜›¦eiÊÁ̔şmF[+]u+MuŒpÇ·˜à¶KØ‚®uÉØúµ©ØãËzÛ—N§ü­R*VJbTJ– ¦=é¶Èj…³Ÿû"£g/ðÄ~/ÉŽ¶ûë ˆð?s‘x{–ØòE! @g7W˜Ð1bNËNõÕ)7•«ƒxù¨ñ¦¿§»ݶRÙnˆÒþcÜøú+œßwD¿rCU]Ou¦“bóÆÕbáÎÍZû[ˆ­ZŠ–M7“€s¬j9'4G®TɶàVð$y½€Ûqë§9ÕxM=F«{ß-1¡½Û2º2†Þ¦PÖ¨(Kˆô³}í»V÷v¬“žgÇãάÊZ«ÖqëmY4­Q˜üM×H$ãäŽàà_|†Ÿøž0޼_‹§ ¼ÃGh_8€Ô5Æ£>26·ô¶fÉÅDŽâAé}kÙ®‘ÁˡۑAZ½tŠAze»QÊO0¼{ç_9¨Î_¸ªJµºÊ&âbͪ¥bÑŽMZç“[‰­]ŽÞžœpœ«Þ‰iÄ%W­«°DÝ|Ózw‹®k|nºé3SgÚ7§1ö+ ´Å…èßÙ–|lMGjGg*>ß18 ŠÕz½VuÅây]í±¸ÃèÐx'Ï"•R•D*Ž®‹™3Î"tãâ‰8W_ÙOî=o£}á¼ûø` j—®S-UÐc6¥K×púº_xùbˆ oz»èXº#p©¥lŒå©;ÍÈî=\x逺xö"¹rU¥bKϋ۠uíÚFbýʰo¢e<Ùƒ™©p‡F©»9&E¿)=€{Ô¶ºú­±|ƒ¶ª1ˆ¢1Ávªˆ™C_ð-ýí\3е#NXº¡Ge}3^LŠt‚x"†R ÝÐq]«‘k%žçãû>¦iL«üRJ|/ ˆ]×ðk¥¡:ÏGÝ/ýWŠÊ¥«T<Ž«éz汉äÃÈàK‰_,S½>Dupd<´y fê6Þ4”^Ì©:Ïèî½\~a¯:êVG–ìÚXÙ jí¶«ž¹Hî…}\y~/çŸUÃãye™†˜7ÐËÖgwi=Om'¹e f_w8žì>L*ºSQ®GuhÔ dŽÉ”`¯IàVÊ.nñûé’y­±~#‘— Tüޏ&úË&¶­nKmëHÄæÛ¦—JÉrÝ+^Ê—¯\Ê•—êFÇÛXœêÎ.L¶(s‘ÔM-±¹e›81‡ÜxL6E %Å| DZÉdShºFn êÕ:f:‚gü€üxË6›ïi #¤”\ýúó¯ ²ã‡>J¦¿wÎ.åz”n f{f®ïïô7¯ÑxMâ©^¯԰ÚÒ¤—/ÂjË¢™£lW¥~þ*ùWpõ¹=\8|R ŽŒ£kýý]¬ÝµUëݵԶuX½ûþŒ'»‡…E–«”‡Ç*×¥Ìs3À$yXàv•^Lùyêß§&ó¦ºý­Êßtö˜úÒ÷t|hU_çãétÂÒu½‰Ô­Uë=í¶¹lSW6HÆl½Vs1ãvgÞT˜ÀÐõ #Lũ깱AiOã8VÓåO¤âäÇ X¶‰i™äsEœ˜M" Bà{RʰaèÄb6ÅÓgxù7þ€'>öa+‡W8Þ€¨JâXÝ00RÉûx×[øò7Åf*I¼Aôa=@ACéëu¼Ë7(¼zˆë»÷pñÀ1588‚º»;xâí»DßSÛIoßp'ÍÑûù"¥ñBq4äl-ÂCNÞ®â7”¹U±§ƒ†‚7^ú”÷ê„ncçïÌêÚ¢w÷µpã¾í‰dl*¢\ªR¯Öɶg°lSB ó¥cL!À‰Û”K,{ò®OÄpë.¦eâ8“K‚¦iÊ$(äKè‘÷OÆð\r©Š”ÃÐÑ4 …¢^÷RbÙC‡ñâOþ"ëßÿ,ßò,f_wSYîåŽÈJ•r¾„fêápŽ¹Ì¦Míù¿–¸C¬¿›ìúèÎCh¼ñ<¼«7(î=Êàs¯rqßQ®_T èìlgË›ýOn#óØFìÅón ÿZ!þè8¹B1?’€4B€‡Ú t+7¾ñóÔ‘Ñ­¯©† u²¬=å}­»Ò¢û©öäckÒ‰'çu¦Ä[”À­»T+UÚ:²è†Ö|MË T¬ÌhBE÷(æK$Ó ´(–UJá{©LlÚÏÚŽaŒçˆ'ê5—R¡L<É…GKÿ„Êøó ×øû_ý=>øåݬùÈûÉ<» =›ŠNwsc²\¡P­¡LͶî]ÿ'5ÞäC¥Í!,ƒX_7é•£ñ&ªÕŽP>xœ¡çöpéÕC\»|ºëÓÞžaýŽMb`×V²;6ã,]€–ŒGÏÂ-¹ÏâÞf¬\õBŠð‡š˜ }'¦ù¹5aç´¼™úVw¿9k®C×Ú»l£ÃÒ´˜+•uýjÁ‚†žYg™ º²‰ +ö¬×”2Mۼɥ/—ªÄ1 CŸô{˶¥*•R•x*vÓò !ȶ¥)ÊäFóØ1;üLtüÙ*‚†i‹Ûø^@½ê’Î&'UZ톦kÄâ6õj Ã2Ø]¯pbÏaÞtâ,oûûo°ì{>@bÇf´˜}çªY­QªÕI$b!~ÿ.ït³ñ&_œàË"âË[–íhãMT«§|ø$#»÷pù•ƒ\9™ZÍ%“I±bóZæíÜBÛ[p¦Bq¿‰”¾)JQ¿6ÄP­>Âä¹t2ÐLŠßªèS]|‰ú{‚_ŸR¤âºˆI…V–!@°Úfß“]™Çµ¥V§cv»¡kf eP¬¹¥r¾\\Tq“hZRÍëJe:2äÆ Ó¬Ww[ Èhë A&›"?^@)E< wyÂå"„÷&’1ÆGóTË5¼º‡ªÕ±¸3mI°ñ½¦e’»1J[GfÆ÷U+5jÕ:š&ˆ'ã¼eõBž FJUÎŽäùï_}ûŽðÌÛŸbÞGÞ³v9èz؆|›·JÖêžep‡ãͦoÿ•7™Ôƒëùo­Õç TŸat÷^®¼¸Ÿ+g.R®TI&ã,Y³œy;7ÓþÄVb«– eS÷gRÑCÔ® ú×ð,=ßöNìEÍPæV"]¡$–®!ô[#›e»j-äç¿64Ñþ»z)V6ƒf< ²Ý”ÛÚ©sŒ=¿«/ìãò©s ebq‡¥ ˜ÿø&:vm#¾f9z{¦Š;‡“ЦÕ:åõëA0¸IæÊL§øSz­Øú†ko*}•穘&zÞÓ“}ûæþη¶gSË2š¥1¥ËKÕ…|IöôujfT*šúœë¦i›ZÒ±,]w 'fS)W©VjÄNø†j Û1gè N…\‘ Äb6¶c‹8ìjÕ:µh:PgwõºK~¼@2•Àv¬I!j*çÍs…”Še‚@’æè5>Óú^Ã2Y6Љïzìɳ§Z¢þé¿£cïÞûV¾uÉÞ®[& •’(EÔŽ<Ãɪ]+z(üü“jõg/‘{q?WŸßËåc§É°m‹þEl~ÿFºžÚFbÃ*ôζ)¨¼×ÒO^‚B‰âàHñšTãL€9nÕE×êÞ·îîÍùðÑ+¤M!bÌŠT†„Ä[:ÒOï\Ô÷ö¶ö´>õyÔ4 ”"“I6•:)ʾÄ0 d Ñ¢’O&›"Ÿ+¢¤"–pЄF"g|$OµR'6Ë/ R®a;6NÌBCR¯¹x®‡½ª•*™¶4ñD €XÜA×u*å ÕJ Ã4h£À¨”kT+UœÈ4ଅB‰b¾DwoǬÐâzÍ¥T,3¯#òyݦŽBPö}Ž}æ œyáV½ë­,}óÉøŒ1­ˆJ¡jª‡8-Uw ##>ЋÝõ€ùòZ[l/\!÷Ò®?¿‡Ë‡O1:2ŽièôÎïcí³»è~j;ÉM«1z:**ïAŠÐþÐ(ã#ãã7­Ӆ“äN €¸ÅßZcùÖ–ÙFç\ ÈZ‚î­éø†UÙäæÎ„3Ï4ôD •ÌU륫…JeCwvu¶-£S=cBjîötøµjöÎ,åR…J¹J:› /Ø4ȶ§)+äÆ ˜–‰i81›ÜhÏõˆ'ãam?ÂäûþäïÓ4X™d,(?-Â4~oÙ&– aÀžÕ÷¦e¢UëtötP«†q¾íX %jÕ:¦i Ï»u—B¾D*“Ķ­I·®[XŽEP.±ïÿŒñ¯½ÄúïxñÍk¦q“¢j¶…0 ê~Dh:ÒwC¾¼kƒ¸ãù°ñ¦¿›ÌÚå1;¼"fžZ«ßsˆëϽÊåÇEÓÝýÝ<þ·Ñ³k©­k1ûzÖk•÷@EÓp¯2\( –C€†˜Vùáö Àí¢ñ¦âí-´]?ÛaèóÞ×ßþÞ5};2éD,Œm(…”ŠUùR¨HÚÌÍ2š¦…Š$¦'Ìð<M×®ºTœñÑ<•rµ¹3ëºN:›Â÷Üz¸“+î•r ßдðópë^³<×ü¾è!nÝ£^«Gåº v̺iç6MÓ2¢Ôk.š¦‘Hưm“b¾LµRÃ÷}2mi ùâŒ7C)E1_&•NNà Ë0A,€nè$3INì=Èà‹X÷Î7Ñóá÷_ºÝÐü‡Rè©ɘƒï†tØåJúÐHÈ—×ÛIjÙÂÉ7÷»l×è«÷<¼+7(î;Êà7^áÊþ£ ]B*EgO'[Þù½»¶“Þ¶>DÚh~×êçTš¸ïÒ5ò/íW*µË„%@—É  V¹mNÀÙ{å;“Pá“@š¨Ö†v[í ÚÞÙ“}ëÖE}'Rñ›.D74ì˜MµR›µDä8•r­©Ð7¯‰h*„¦idÛÒò%<×'žphÃÐ1ŒžçS)U‰ÅÒÙds׉!º¯V­ßô}J*Š…R˜ˆ;Ä“1J… …\‘T&¹û“—\žë6±B„Þ@¶=ÍèðxT90ˆÈðX7­…[÷BïDá÷û‘‡'†®c;†ÞZÏõb6§ý2•¯~ƒÄÉ“d7¯¥oózæoÛ€“I£§“´¥ Ž?v†ô›iÛ¼3•xÀ7Q­þú¥ýÇÚ½‡Ë{3xù¾ÐÑÙκg£ï©íd‹2c¯aTÞý^«‹ðᓌ|ãUy~ϡ⡋WN}ÑsO1nÝq0P§ñskM¾µÙ& ´г9Û°:›ÜÖ™pÚ†ž¬×}³#étÞ¤ü‘(¥0Mƒ’vÄ5šb¦Šs¨Õê#ø¦‹2 ¤”!L×УL~šZµN¹TÅs=̨ì!òÎv¬æ.„¡Atµ¶c3:yìÔÙàt©TÌ@õX†þxÚiPF·‡r¯A:cò¯!Ó€™vùé’{­tY bÌž·weÞ·mQß³éLrâ)oºxÞlçCàÍ›F ­QoTÍ¡ko;Ã7Fñ\Ó2Ñ£N:eÙ}? ?V ‘Ž D®|ÐA¡é¥B·îâÄŒhnxõZXÚK¦Íì­Z§RT­r™º°†Ž‘ wñ†Jg’áN=Íå+V rc<ÏÃ40°Lª•ÅB8ß¡VuI¦ã$Sq¦«î)ÕjX͘ªüSʼn9ÇÆøÆ/ÿwœªÇ_2~ü4½®‚‰îǃœ/R=už±ç÷rí…}\;užb©L<gÞŠÅ ìÜLÇ“[#Ú¬×@玖«uøçÙKä^:ÀåÝ{Ôñ§åéBN–ô@uZº¶1eYó”©%¥Žî‡Ÿ½¦{µOÍ: ¤U¦€©]vS 0¸û™Fœ0Á—î×´žu–¹x¹m®ííÊ<žJ'&mqJ),Û¤R®ø²Ù/?éËE¨ ¦e’iK…~¾„¦ë˜–Ñìºóý DÝEýó©LÓ4ðý ‚à ŒDÖ©Wë” e4M`˜õZÛI…%/–e’mÏP«ÖÉ¡”BÓ´ì£i†N¶-ÝÌÈ7B)g‡úBÈýçy>ÝmäÇ TÊ!ÐL÷Ä´LœˆY¨4LƒT&Ùô@<×'ÝlDZ’õZTzvåoÜ“XÜáõß§"ï?x‚eW1Í»4átOq Jeêg.2þÂ>®½°+ÇÏRȈ9}Kæ³yGЙJ›õzèÜŽhB€¬Ô¨_‡^Ù½G8zJΪ¼æÓné¬IšÆ< ¥oh¸ —œŠPÕ| «ÜL~Ë*@«ò7êö­;|ã•´-ŒYó8Ö¢e–¹x™¦/IK•½.sìXÃâOú2ÓÀŽÙò¥ ×y’ü hƒ³ñ\¯ùð‡Ê_Eí…1u,î„J:7àÄì˜ï…Ÿ’sà4.!À÷LË$žˆQ«Ö‘$Ó–šv‡W(lÛ¢V›Œ*l• Ô*5Ú:2¡‘ʦÈGPäXÂivû5^©°´çÖ]œ˜C¹T¥R®b˜&z£3°æ¢7Ï ¸QnAJ…[÷B¼ÄmˆRв”\B¢’¯Ÿ¿Âö/í¦û>|WÏqs÷*W©¿ÖêwïåÊÑSäÆò˜–IïÂÖ¿ï-tF´YÆC¡Íz H£ÄY«ã]¾Nñ•C\Û½‡“‡«“£Ã*'<2–ÎÒ„©ÍS1‘–º/&)ýT)*Y)øT™ÜtÛ!@km#¦Ï4^iMt¬5Ìy‹siGGjm_GzI*n§L]×PPõ<)‹?˜…Ÿ-™Š‡Í3c’©8¦eF† ü€iaÌÍDÒ+Œkm<Ï'?^ Û‘!sB2KÇ"7VÝwSŸÄ&0£þûR¡L¹T ó‘"£¶ßl{MX¶I!W$?^$™N4 Rãà**±Õ*5ª1{UØ"¾ç¡é¡'¡TdÛÓ”Šª#9ìhwBžbÉXä{>^ƒ@lÇÂs½ØŠ*å*õš‹e™X¶…¦… Ïó›‰ÎE@¥Rcq:ÁÇ“qªžO½\ãèÿý;âW‘Ú±¹©”·õ WkÔ/]#ÿòA®ïÞÃåÃ'ÃÐuºçõ²ò-;#€ÎŒÞ7@gòZµP‡_¾NqÏan<·‡3ª“Cƒj—¤¥‰…qK<©‘¹ ¥oˆPD–j’³t¶JÃLÅê;„Êß½ÐÐm±Ì5kLsMŸ®-6 ½¿Ò•îLôuhÍκhªCLsbŽ5:<fÚmsÚºt&I­Zé±…@×uŒÈÅv]r©B"ǰŒæçÃÚw‰XÌi‚r”R¦A<#?^ IaZ7§5„)…\)t« Üx!Ìè«pÇlïÊF±tŽÙ4ÕJ5 A„@7£}©ðýp§MeSTË5‚@G4`¢R;YtC'™Ž3:4޲Á÷|JÅ2±xŒXÜ q‘WÔ(Z¶Ù¼EA0>’§TªÎ$)æKH©È´¥›mÌ$d1_²ͨÃqú›_-×ð\ŸÕ‹ûÐu%u×ãúhžW~åwØü!ûôchñ#`Rý¿Œv¯Â¾#Üxn—gäÆ0Ý}Ýl}ï[èyj;é­ë0ûß –Z½mˆÒc =·‡3û«S7®3,k*fibAÌÛTRd¤Žq›Jß*ENÉ‚ ¨s›ÀÔLƒ ;tÿh*ñŽ–ù‘q¥œ“žï÷|wuOfþúóÛ§k`)ËxuM×ñ}Ÿl[:„§ª¾YA!W¤^s‰''@:ã£y4MÐÑÕ ðÆGó´wµM›A¯×ê¡'àØ8q›é'lòñ\Z-D×5jïÕJj¥†”]×iëÌL»Lá.[£˜kûü€ihZ˜•/GœáßF" ^­ÓÕÛÑÌÅ¢œmO/ iZ3Οù”K!¤XÓ4d š˜±RR)W©UêÄ“±¦g^ø¾O¥RÃ÷|Ò™d3и/JAaxœÄh™e›ÖÑ÷ÌcÄW-Åèî‰1„†7 â ‡zµŽ®ë“à¿J…U„T:Ñĸ®G±X¦­#ƒirE2íéfÓsýfOROÆÉå±ls¢àæg ·îR)U£cé ßrÓ#&㉺®3…º‚’|ÏG7tlÛ"•JLê¢l<Hwd82Vä>óeµò‹ß`e[†¾Î6‘jË`è:cWnpãê ü  ³#Ëú'·Ñ¿k+™›› :JñÆè q ç_=ÈéË—Õ ¯‚i úM“§„–•:æ]ìôÓ~5à ÅXÐäœ-¸‰xjéÏâ5)Íî¿W|KGvÉÊ?ûì¼oœ8ã: O=b˜ðªÓÖ‘‰šu žp‚¨?l 1ÍÐ  =Ñèz ‡]ûx<†³¨UêTJšofÎ[ëïB„‰9)ÃD]:›Ä‰Û lJQ¾#w°«Ù3Ð8©Ð8¥ÂR¤ëáÄœf9S©ðüjÕ:µZ½YíhäôYÜ{ *_ÆI$ãHž›ïù“€K3‰¦i¤'J¥—*´]¾®– !¶Y¦XÑ‘eÍö Ì{r+m;7c/[x÷Ã,¿™¥¡ôRŒå"\Ã^.¼²ŸÓ/q£^Vš }†)ž0´Ï¡ÒO•ºP~>˜–ô¶z¦VL_*CoKÓùÝïçñ][­Åóe륯|£‰¤kHàûM4HJ‚~I*“@ (äŠhI Ó21L#©ßŒô‹²ñ¾ç“J'Ð4-bâ‰E;`…R¾D¶ãfæÚZµ&ÑRqr£yR™DÄÎ3%‡ßÛzã‰BäsÅÈiÓ$ôü°¡(“¤Z®Q.Vˆ% crÏ÷ÃjD,âHE±zn¼€s¢Š†~oäÎØŽ…”’ñÑ|˜Á‚@FŒ„m¿ÙöLÓ¨‰ø–5ýŒ7úžŽ®¶0Ï"ÂêG“uø~¦ª~€’Š^`½iˆÇÛ2lZ±X,zb íOnÅY¹=“zpy^+2•wâcÏïåÒKû9uþ×kE”½¦Ác±¸h(}c]ï×*U…ôÇ•g–Y€SeV€D ¢z¬9¯y?úQ6¯YÊžßÿô`¢–/„ÖLÊ¡Â]ß²Ì(fÓ º¡S.–›‰­°L5qnJW÷(äKÄ“±‰Ý¸¹Ëj!_ˆ ”N†‰¯è¤Ãÿ”ô£V®…‰0Ó@k€{ü€zÝ#ð}œ¸3ïW-K8ض5©ÔV,”±m 'BáüxQæÞ"iMFø *b½æÒ֙ŲL¤T” e ãERÙ$º®¥(l°‹ Œ “jO`ZÖ´aO<‹IÍ›J~B¼zh¤eÌF´áñÌV&BP-W1ÆKü`6-6-](–ìÜLû®­8k–£·Mi¼Ñ”>D婼Àø û¸üÒ>NŸ9Ç•Je(º ƒ­±íÒÀjQúf¨ U+új*ð€†(Â#L„¤Ñ@‹ÅÏì V*sì¯>‡é»˜¶ÕÌ^»uß“aÉtà g;º¡S)U¨UëQ#Ž%B » Éx#·®½ V«cÙ&élŠr©B~¼uØ…X{ßóÑ“!µvȲcâ¹~ôÞ"±&½wè-tö´aš¹ŒD*Že[T£ÖÜz¯^sñ<Þþ®&ÇãÚlÇ¢˜›q,+t-ëøµš‹[«“HÅÖ]@×ÅMý`èzóºÃÞ·î͘p"lC~¬@"•À²ÌI…‡0é6ùs†¡cE‡élrÆ0 V©0xÇÞÁÀ[‰½!È4¦‘V¥ÀL¹÷så…½œ>}–+¥ž.é2L6Å:¤ ù0#oF¬¹µˆÑ7t±ëuMhÍ1ZÓßï30‘ˆ‘L')ÂfŸ!HQ»oóE†nŒ’LÆ'•Õ‚ ˆªƒOÌjv –‹bÒ807rݰæl{·îE]~a]¾^uC¢Žˆ£ Iù-ÃQd¶“Ás=J… ¥B9Ì‘LI¨˜¦A[{šñÑ<£¹2éd ËÐC^E!ЇöëYõôNºV/»©Vÿ:ß㣇 …Wðê åÇ|nçáÂàuÆe„©Óo™lRI’RGóoCcîDZ•¾bë ú6[´-1qÒaïÅè9— RÁ>¬ÐïÁÔ„tÇ|5hV™•Lq3ýíˆR Ý2Yúô>¾™âÐ(å‘1*ç/3ö™¯P9s|­N<›¢£»íf°aLŸ±Ld4|#7š'™N`Y¾ï7Ñ€“yáNX­Ô(—*ÍÊ‚nd(Ë °ÓkîÚ¾‹ÙÍî¸F0³±‹D*Þ M€fÒ¯V ûbñpºoÿÿÄŽÜZþ —aù®Z®âGuz)e3/ÒÖ‘ÅvBÔd!_ŒŽ6 i"LŽzžO©TáÚpŽÜh‘­mi²Kç“Øºžìú•t._Lv~8iW¾Aâùèahâï¯ S>pœ¡Ý{¸°ï0çn\c<¨35úM‹µ‘ÒOí©Ÿ Q@ÝÝ`¯ÕéÝfѵÚ&Õi 4ðjaÈe9:É-q¬¤ÆÉ_.’¸*î:¨Y+xªÀÄÿpKNÀYúzog!¤D7MÚæ÷Ѿ`¶¬Ç{zKÿþëìÿë/0¬Üu¢éŽaOƛՀj¥ÆøhÝkç Da‹¯”"“M‡e@¥Ðu­Y~t]¯¹4•Rµ ìID»vc7ü€X"Ö,+N)ehdÚR ÖímkÒûê5—b¡„sȦâhºÆøH)C˜¯ïù-<¿åXƒ (•IR¯¹”KJ•:㮞O¦îÑ!;º»˜÷¶7ÑõÔvWavu ¢¼À&ƒßªôC£Tdx÷«\Ø{˜s×®0ê×° A¿a²ZO’ºJßÏ+¨d$µØæî6Ñt(\x¹Âøa—ê…psîz³ÃÒgâô,µ¹ötúŸù˜wiJ¨Ja2`ÚqàSev`¸·õ‘ Eø0êYº?úAÖ­XÄ7~ýwn›iV74ܺ×ì“×u=œç'®ï‚J©B"oun>†NÌÔÃÖå˜ÍÐõ,ÛŠ’‘ïB´Œþž&“1éšF,ö˜–AGW¶‰d ùû$…|1¤ïŽMÐw7Â'fG­Ôá]r”ÝÌÔk.V:. ç-Ò)4Ö÷÷°lç:ºŸÞNróZ̾.„i† ÿ†©ÕOÓtóü^.¼z€sW®0âU0 AŸn²BK’Ú]µ×Î*ÎÊ3$^»"¨C¦ñQ¤`á1bI+û« ï©S9ä#®‚YØP‚ë§*1Á²§tm¶9ÿ«,Pwh¤€‚’ÅŠO™¹õî<pë… A6ëWÒ¶b¥ÓgˆMC¨1õ4<×Ç0 ¹JɰñHך§6ÙÍ @3–0±a(h²ðN}<Ãs}òãERéDØM}^ ð<˶BÂO©šü{¶c“ÙƒkÕ†aDÔÞà&Ó K}NÌfj‘Ã4°32:Ïühž5N‚eoÛDÿӑܾs^šm½±úê[›nFsTft÷^.¾z€s—.1ä–1 Anò˜çn{êgAÖu‰×Æ ¶­ÙÕ&g~¿„Ú%õͰWcø¢Ë™_+‘ш£…|-˜°x^çú—jÌßêê5Pm JÜq Q”ÌûòÎ0p[9€û2R +cËwˆçã÷©bÇœißÎÜ fØŽE©Z§£3 ¨lí%P81‡R¡L,áÌ:R‹£/ÈZp™¶•R•ñ±<†aLŠïÝš‹ˆ…@VËÈñT:A¥\¥0^ ^÷"n¾–,ž‚x4M¸V ‡‡ÎÔ©X­ÔèÞ¸Žßù-$ÏãqàOa®;Ãè û¸ôò~Î]¸È`½ˆÐ¡×0Ù)ý\wÚ‰ÆN¯)¼6…±L#³Å¦k£Mû<ÓF}T½å3–@3 ¨*¬ªÀá®>õ6k%¥±€XZGKÝûï ENÊñ;ÅÀ- €ºoœŒJJºV,機ùAýÅg=z-I>Óge É•*”ŠúºÚ<ÓÔ›tXSÅv,ܺKn¬@&›jöÀOº¡Æ–|DW*ÞXJ¯2W¤v2d¾üò~Ξ;ÏõZ¡+º “ÍVœ¶h§‡9NäÉP±Ü´B_*Ho6éÞlÓ¾ÀŠ êÉð9—áuò{=ô³*ì¡ é‚ÀUlÓ]* ÊP/Ií:š#"FŒ;X.ÀŠ| sÜ!nÜ!àÎYѱxOýä÷3tê7§põ:~µ†fèÈS¹zö,_­ÔÐsô9&[ú;™É°~¨ õšËøh;faYV‹H‰[ KrñD¬™lËçŠ!†a ìÛω'É8…\!©t_ÖuB®ˆ”ŠD*6©½YÓ4’™$ã#¹&”·ÕÀ˜–I¶#VR/ Åbt,š™ˆ“ž×ÏÀÖõô®^Žnš¯ÅŸÚiwê¹÷sùÅ}œ={Žëå<Ò•~££=˜Ü^;OhìƒKwµåŠÌ“ 6Zt,¶pnU1vÉeø K~¿KpFa1)šÄ*JÐÜêÖI®šøì]H](ÔW Àmñ4äá„­ßU úׯ¦ýj¤ï7鮂‘qÖ~îYö—ϧOžããEb¦N{{ ÜÜߘ”‹9t÷v ”¢^w©UkQÞkB€3í±¦Q‡…fš†ºÑà)PQ/QËpØN[«Ôšk‹vîüx‘ñÑ™¶T³¶¯dÙTˆ:ôœ˜ÕìñoÜq]ÓHö÷²ñ»>ÄüÇ6¢i:†e†\)ïûú?4™¥Óîìé³\+çð5E§i°ÎqîK{­ˆbz_(ܤÂÍ(âƒ!¥[×ûÖ¼+‰_WŒ]ñ8s(ÜéýÓ3'°eÞQ ÖãšMH/üŽ[ž‡6Þ¨ºBÜE &¤›š€ÛjnȃOÎôEQ­ZhzäRk}]ô~ÿ‡ië¬ýË¿ç¥Ïüû††¹|yˆyÝa ,!ü¶˜/‘Φ&í´q3Ö\††pâöMù]×Hg’aÇŸïS.V"Æ"#j s 6ÕruÒ Q˶èêíIN£‘\FÔÇ •BÕ\Œ| }¤€ZÔŸrÂ]Ó°ÛÓô®YÉÒ·ìÿßyÊÿàcæÀ 4ìèüÑfö±Ã$`è9ûy+ GëKTùN€PÊBÖr“1scT£fuÏhå»”¨ŸÝZ²€y?óqÞûާØüçŸãä×^bøô´®,Ét·æbZæÍ5ûFÞM†­®^ã óSpô C#ðäS¶#ƒ•äZçò¥ÛÒrE|? Í"hÌ00ívüêå!Š… KM“y]ô<¶Î]Û‰mZo™xµºiˤ°ÐÜ×1X§9+ R£vþ2…—…v'Nq%?FM h3tVÙ6RǾM7*Ï)Ô€ ¾A§w‹Mçr‹D[H­vâK%„4Â$ž¯¨Ÿ Hk¡¦ÜÖ¨…f…^^àξ£+"VR£–“PŒ€mwpY (#Ë¥;ähÈ­p¯Á,R‚Ä6­fɪ¥t¿ò&®üÙg8öÊAö»Ž3Ù¼¸Úú^H.jÝm¸u/$Êt›pèÆD ®”²É½§ëåR•z­Žeà C§­=CµR£T¨D»šÀ­Õ¡T%^õXÓ»k=}Om'õجù}h¶Ý@E̦ª‰³x]ºù­³.^¥ðÊ!®?¿‡sÇNri|„ª5t–Ú&]2vÚk§Bq×ôoµèZa‘êCÈüÏé,3¶Ç¥~8 hÔ,‰f…ŒÒDaFiEÿjNØ "]5k Ñ!pR£§\´wŒP JË“yæ,xM=œ*Ó ýôvVnZCï×^bàO?Ãþ#'©—«$§L–R’/N´¦i†p_° Áu¯i·î’L%šž„¦…ä&Å\Èú‹;hº@èZ؇Ÿ“Šå±mµ€ÅK–Ò½k[ÈŒ»dþÌ̸¯¡5Sií´»tÂÞ#ÜØý*玜àÒØ0e<2¦Á¢Ð^ë ¯Gá¬ÕéÝjÓµÚ"Õi kßS\x¹ÂÐ ujGôA°\ABDùtS Pþ4TtKÙ‡}?˜”ăð:„"ùÚ–Ô Hµ\£/…Œ¿JQõ|DÕÅ,”I)¾Eóh{b#Ol%»zV[ÞH$™SÆ[•öep÷Î<Ê…áA ²N2ê´ë•Iâ‘ÒÃ*~cÖ&Ú¤S)…Ù¦a:‚K{«Œì­S9PËÄþu†LŸî‚®M¿ã*šz²®p…Âës¹Fv«M÷z›¶Ýä‡}T…¦’7<éΜ<“ÔAÏz›Á£uÄEUn_<y_Žs¸ƒ@ÁØxž_úåßæå—ðs?óý¬Zµ„àaïrQ ^4]?þq.½z€ _{‘ÜѸ¹"z"vS"#‘ŒS*–Czò)`¡Ð4½])U9½â±r~?óßµƒö§¶“ذ ³«"’L¥¼ÞI2'µ×S>pŒ¡Ý{9·ÿ0o0.ka{­i²á~µ×6DA%-16hX u*_ðIŒiMmš tÂgÿ¿Í¡]³"ˆ+Y ݉®Õ,õy¥…!€ oÔH¿Û¤{£MÛ|ÓTò’kGjŒìs)ðÑ/ª €Í y¦€PPq$Ýïv0mÁõ¨aWÄ$˜ðíJ=¤Ïq‡< ¹`òï|?à3ŸýGNœ8ÇÏýì÷óîw=ƒiaÂä!Š’ñXúÌ=¾…Üc\ýÔç8yè(ÕX•Xƒþ›°W’|DKÖ€ø6zæ/äЕÆSÏ<ÉÀ®m$¶¬ÅìíF˜F‹Ò¿:Q§ÝÕaÊÃöÚóûŽpîÆÕ–öZó¾¶×NO(ï5Ùð‘4JžóãÈçU“TCÈ^ÓQ¾UiQÍ_Mé+Äl·NËxñ´ÎÎlðµ’dðTáý.Åý\PXåȰ´PÁ+ºqDxÓx*–$öƒ%Ï$8ÿ|¹G¢‰»Ð~ *”›ó§5·%·5Ê€‚É ÁS§/ðS?óKì?pŒù¡ï¦§§ã5à DùÓ cÇ&ÚÖ¯¤ëù=ìÿì—( âÄ,ôh˜G"Ç´MŠùcÃã8BÃ2uŒd‚ä‚y,Ú¼ŽEm&5¿ñFj¼™Ú^{ø$ÃÏíáÂÞCœ»z•‘ :©½6ý€”¾yz ¼¤bÞf›òx@ª]'»Õbüå¶œ<“–’š0B×^Boÿ\èQ aø¼Ëо:…ýêœÂ, bªtó„{4[€THW¡!&÷ô+ÚÞk³ò= F/¹Üøó*ñªvÇÉ¿†”…¬æ'fÜ6HCní4œfÕ[çè•J>ùÛÊ¡C'ù…þ ¶oß~î5åVDÄúÞþÙ­ë9÷•Ý\øò×)_Ä3tjJ¢×}b¥ J£gÉBÚvm#³s ©% 0£d⢯~R{í(•ÃöÚƒœi´×š‚>Ãd§'}?:ínS”1_î38ðÅ›žMÓ½Áf¸³†=Ȭˆa„/ÙÚ-?ÃûtS0>èqüW Ä.i8S”~Æ'\¨¦TRIj)‹ém&;Úç[\?QçÌÿ(»$¸ËÍ”å¢ww¸u/@¤Ì!ÝõŠå‹xú©í|õk/MŠ›w?¿—ø¡Íÿ³Âw}ø=Ä㱇› lž˜(ŒµeXûíïeé®Ç¸öGÅ_þïOóÅJ•Ÿèe㻟¥ýÍ;ˆ¯]†žI¿qÆ\Mj¯§zô4#Q{í™K—tËèô&kq²÷Aé›P\¡P:èžÀT33ãø(ë „Õý>¹•›¬U:ò†œ™[OîìºÀs%3ŒŒT€† õ’Â(,Âøüv4*Ì„ÇóÎGLº·Ùt,±ˆ¥4ÊãÇ>[dôÓub×ï P EQÉB5hbmÀs\Œ2®‹ ð[¿ùoù½ßÿ þàþ’\¾Ø|ïõëCü›ûߨ¿ÿ(?ýSßÇÒ%ó~HиŽ€ãÌë¥û‰­xÿëÓhžÏŠÇ7±àŸ"œYÿF Îjm¯ÏS;~–Ñöqñ¥}œ¹p‘õÂÛk³ïO§]+×M(˜/ˆ¯Ñi[l`$4r'=*_ðˆµ›Œ€¼˜¢wƒEi0À¼ ?æ²ð±mÛLFž¯á³(T3@¨.3‰.†@z·ÑÌÓXœÈfÉ(`‚mßÅt¾«¿âqæKuÆw»h§ Oã^ñµ€œ’ù ¸ 0W`2[…”ŠŽö,?÷3ßÏÖ-ëøÏ¿ú{{žëµÊ€Ã`«δ›ëN;øû˜B̓øƒ¾­!7žÑÑõ0æöwHjîcMÅ) ªÚ—š\þF§((õ©•$Ýkm»kp™+ò¤¯P³…úD²pFÐ-®·+¬QA¢Æòº-EaÈgøHñ½.Þ ‰9± ,CÏx^ª».Â]45Æn½ýÙ'Y¹r1¿ù[ÿ›OýÅç©Õ&Ž=ÍýÄ/òñïûv>ñýßAGGöµã D¬¸†ŽÓÙ÷à‚½f¥ÙS2j¯q?—_ÜË™he Kº “MNŒŽû8ÈRuK"ûC(nÏV‹®•6ñ¬Ž[•Œ_ô¸´» ú·:d{Læ=ãø X£“%@á¬6°âù#–Ò¨\”Œ_öè^nc¯Õ ®Ê™Ùuu¦‡è¾Y«F8ÃQ6Tªq¸iX~çm·6\ù• ÔÃàò+U®¥Fýh€1¦/°ZšˆæJ<¡ÔØd À\{h É‚ùýü‡_üI¶m]ϯÿ×?äÜùËÍ¿çóE~ý¿þ!ççÿù'Ø´qõÄá‡)Q ¯Í00;ÛV›ÓÜKk{m±LýLÔSÿÂ>Μ>Ë¥Rßtœ°Óî~² * Iö{m팑l7ðݰ§þÜ—Êö{Ès ½æÆŸvÙú“Yí:¢MÀ“îg)z6šTƼ3[h˜yÁÈQ—þ5[-¿^E÷f¸©Fc¬úÌ}Õ|_ØÌ£ ×VÈžå·g‹E÷š –ß ûªÍ9¶+ð?+Ñ‚Z<×Jß*5¤ßRœêÌE7àìíÀRJ,ËâÃßñnÖ¯[Á¯þÚïó…¿ÿF³R ”â+ÿø"§O_à§~ò{ùÖ½DZj‚PafÂ21R‰û‡G}Òh¯d¹¥½ö…}œ>yš‹Åpe§i°.fÓõ0fÚ9пÑ&žÒ9ñ…c/ºgnî© JG$…!Ÿd‡q³Ò(:¡c¥Åè)}4¬ë›RP8èQ¤k͵Þ*\ä¦Ï+¢*€€™˜zŒøæŠj¯ÂX®ÓµÕ¢{­M¦'œh]ñ9÷|…ѽ.ÕC>±RxºèÜ9¦ÿn¤*T½eÀïþp{8€Y›W»úÚµËøo¿þ/Ù¶m=¿ýÛÂàÐhó=—._çþů²ÿÀ1~âÇÿ) æ÷=´@I‰†¢Ý2#Üþ7Ÿh¶×–«ÔÎ_¡ðÊA®îÞÃé§¹˜¥*Ú-ƒÕÑ ËûÒt=²±7—³åc!¥ñ€Ÿª’º¦#ôzêÜ •›{ã¥RXË4âí:gz˜QãŒ&À?#É]õè\l[o\ B%œºnQŸ¾ôîé¯K³ÂÏö¬´iûw&ÙÍ”ÆBj.ÕÃ>Ú5°ê‚¤¸÷„ÞÝHYÈjÑ¿;€†ÜÒ¸]—=$Éd‚þÁïbó¦Õüʯþ»ŸßÛü{½îò¿þ÷ßpäÈi~áç?ÁÓOm(MEá¨kˆÛZ"þM£ÿ­=õõK×(¼rk»÷ræè ÎçF) Ÿ6Sg™cÑ+û6ÓNª×o}ÀêU­z.@?­°ýÉÊ ´Pé”TèJ„ 3Ó(‹T zɃÁãu´±É½ñž®hÛhãUµq¶ëzF˜X¬ä CбÕâÚ?Tˆ»â&ã"Ì0L’³ã¬0WlשäáòZØOpÈG\«*H0¥‰è‹Š! • üŽ`ŽùÊüÄÎÍüÎoÿ"ÿãü9øÇE©Ti¾gßþ£üðü[~è¿‹ýÓ‘ͦ¬7±ÇbZÌ~më³½¶†{é:…Ws}÷«œ9r‚sc#ñÈZ:‹“^»?3í¢¾I…5Ät­³ÉôX±°—¢œ 8õ·%ªŸöpÜ­™÷™”N(¨š’Ž7ÙŽàÆîV+6^ßk-FϺ¸ƒôj˜«t:¶™t¯ ]s(ºVX\¨ Îr3UW4¢Rz³ôé ðÉåC5ƆX.G°ßi¨½–ÈÉ€©  Û–Ûaºã“ IwW?÷³ßω“çøò?ð<¤R$aY¯½Þü†Ò×]¼+W(í=ÂõÝ{8sðçF†Èá‘¶48}ʾ/M7 €ŒŠZF¡ET؆Ë YqËc…!Ÿt—AºÝ`í‡S컑#øZËœ;m‚ó9|Ó€ÈVm‰ýNƒ¥Ï&¸¼·Šûb@¼%žP ôÅé^ƒü°Oï÷ÅèÛàî I=r—=޹Äü­1Rñ þÙcŠ«!Œ0L‘î,ÆHƒàäò¡rë/Ä„1z­<-€œ”9%©qmÀ ™³`ªHŽú6 cÆ¿ÿÝç¿Æ‰“çøÙŸþ8ï{ï[°¬ûßT$k.RɰȲîý€s!ÍöZïʰ½ö¹W9{à(g†n0®\¦Æ|ÇdÛýlº‘!^½fIbž†›RôþxŒ…[bØqzE2z>¤Â.ò ®JðÀ\¯±æc)Úú-Þãâ«eå° ‰¹ò2Úu£—/B X&è~6Æ¢§bŒœó¸üGâ…Éa„/©õ&NLcÙö8~ È_ó8ý÷eÆ÷ºø§$AQaþ„FÛÛM:·Ú\úb³:1eG¡F4Ìõ{eª†5·„ý>$i`rrÚàT™õôo¯h–Œé¬ŸWjRïÀtröì%~æçþ?ö8ÆýÈ÷Ð××u_C‚ ²YŽƒ0oÙ }ÿ¤Åõ|üëC”gð¹=œÛw˜Ó7®3&ÃN»yv8½ö~7ÝHÕeŠô&Å3.΋€ ,4!8ú™"ã/ya?fðEÄ ã~Uq2VbËgéXjqq^u"ºÌ¨³.ð~ ¨Ä$jaHÄÙ¿Å¢s©…n.¾PåúŸV‰]“ B—Rt¬³¨%_¨0¶ÇÅ;)1FÁô5¡(ÅøïM’Îå—æWP'™Ô§/L¨×%•±1 Üøµ¨ôSÅRŽ7€æ> YîÆLgV®XÌ¢EüÃW^lþ­R©ò?÷S>|’_øç?ÈŽ›šŸŸ[Qõ:B)¬¸ºö`C€ÖN»Á*‡N„=õ{qúÚU†ý*Ž)°,Ö©Äýo5ý²PÍ*–üp’D‡Nñë¦xB€RÎgè¯ê$¯háP––Ú¶,¥—ÆÞãÒ¹ÄÆš§!G¸|=¼n3¦‘|ŸIÏꈈ3Žg÷=ÅÑÝE®üY•öKáï&š1OоÈäü7* ²JÌÓ&k )Aõx@aØ'Ûk’Üdàžò›a€Ðùs>{)‡i¥ ç!ɧg…Õ¡á ‰=ü@{¿IûG³øž"wÕãêÞFLзÞaõŽ$í&gÿgç„šÔ ã£ˆ¯30lüA˜«¡éÓœ„úŒžòèœoѶÉâÒç<ÌJhÒg5ÄiÂÞû»,Û‰©i±‡dGªÈznpÇmÀ ¹uàœ!¥þw^‚L:Åüðw³qã*þÓþ]^~å`ó#ü»ÿ[ì?pŒŸýé³|ùÂ9 ”R5¡ÀŒ9a=ú~Hk§ÝXŽêÑ3Œ>¿‡ /àô¥K\¯—Ñ̰ÓîI=AÛ}˜i4¡«f†Ç*¨§ ßêP ÈÍ%!µfü+´ˆ=ç'¥KA},ÌòéN{7øõ4¨•$vWÕÅ;%1rè0ü¶:¾/üu1Ô'àì.‘¸!šÍ5æŸÊx€{&Ñu3ˆé n<_ëIF_rÑ‚Éëž:ªe–@ˆ¸@VÚ85í#ÊKBVò> €VÐÜ…òHÀ[®Ù4!€nDì©JñÔ®m,]²€ÿþÉ?áÿüég¨Tª@8èã¯?ý%Ž?Ë?ÿ¹àoß…¡ëMÐÉ]‹¯^§ŽÂpìðÉœ+imºÉ¨ž8ËØî½\|ù§ÎŸçZ­ôÇnnº™+¥oè]Ý”}!tµvU;¨¦m=•J¡¯×èYcsê3%ÌË„JÒÀÀ‹ðšny‚‚ZD׺¬z˜}=çrý÷ª¤Š:V„i>T¿pª¯Äú¥Xí0þüï×q-lþ郎¥&Ã'\´áÈ Íp ºx/HFŸ¯ay⦙w+R)ªíŠØ.ƒ;l2Q%Ä«+×¹þgUâ—œPJ¨RÙŸpGrË2`c¦ýºLB„ '§ìÞ†®7Ä ôõuóoþõ±eËZ~õ×~Ÿ3g.6ß{üÄY~ü'ÿ=ß÷±oã?ñtuµÝ›7 ~ÝE w"ÿöŒJkÓM¾HíÔyÆ_ØÇ¥÷qêlÔtc¨pzm,jº¹Oø{©À5%¾¡HTu*í’?žbþÚ××8óK%ÃS_ ê1Eÿ³1êEÉøW]A ;M´{2Á>Û9‹‰cîÄEFH_aʨ§ñVޝ‘û—ëkjÌ[cÉ[âìÛã"÷„ÏŸ³ÊÀÉèŒv1ë·æÍs|­yîs!> w#,ùž$ýklL«…CRAæ-F\på¿Ü\½¸_"Qä•,Ôü&à®”& Àt*®îͧÝLá c2LJ‰iê|èƒogÍêeüê¯ý÷ù¯7=‡b±ÌûÍ?æà¡°©hë–µ:ù.Ϋ‘#æÜqjÓÍé ä^:À¥örêÔ.•óøº¤Û4Ø‹Ñu¿&Ý´H€¢¶zÞá0´§ŽúGErHgä%—•}+FÞëRú_.¶œ\_gµ o£ÃÙ/•1.1YqZr·ôè±pÓ/ʉÃè·#8 Y¡}¡E2k0ÿCq..£U w“‰WSTŽwE›}/â+…ÿ¸`ý§hï·¨–nœ¬R¸è£‚ž 6Ùnƒy›bÜØXC~CÝÁÇmŸ—PB*ðÖqàsê(Âû.'òw~aR*‚)@×õ›\¥P¡%«W-á×ÿË¿`Ëæµ|ò·ÿ„¡–á×¾þ §Ï\ä'âc|Ç·½‹X̹ó¡RH×'ÙônósSgÚ»Lþ¥\~>Äß_(ŽSÓ$–Áú˜MÏnº‘@û‹•oOR:å`JAéï=.­­²tg‚¥ïH°ÿ ‡Ü«š™öº%é~[ ¿&ÿJ¸¯Ýl…’[,µÔvV#ðÞ°ÄTƒ35Á¬µw]¸{$ž«°úIæmty«ËøK.v·Î…ç+ˆËwN›}/¢$Ô—*Ö~Ð|pOð¼iCcæ¨#Q?Á}7®æ?ýçÿÉK/hþýêÕAþå¿ú5öï?ÆOýÄÇX´hàŽB%%Ò÷1…@9ÜÒha\'+5ê篚nN;Å…ü(e- ÃÔY³é•q©5›Læ·>U)Y“èB@¼ qåSUÚ—X´õš,üŽ8çÏ—IŒGaËRAÿV‡Kߨ Ÿçf·Y#ÊkÌ~ABAà@¢G§V”ø7$–ˆ\e=ÂßËÙóɱºÆÐßÖèZg“ÈêX 5ò¯úœý­úµ0Ñ6'ºÕÚÄ$I;§ ê–¤ûý ,N|±ÄàÖˆ -<ÔJ.?_¥ýÃ&ñ>ƒ1³Žpï¿Áw…’¹ "»êhÈtÚØLFû y/eÀ™B1‹›Ôø¾]Onaéïü{þûoÿ ÿçOþ–r9Lº®ÇŸþÙg9z4l*zÓ3ßë¤$ð}t-2ÓFëx«Ë×(¼rˆkϽʩ#'8;6BIx´YKc&ý*½hÁßÏ¥xBáfžPdÆõÙm@ iEóW'àÌß–Øô½ÖÇ~‡‹û)¥)Úßd£”bô+.±©»?aŒ.y ïÐé~ƒÜ%†'Ò+B ÏM·•­}Qpä·òè¾@]PÌ«XAX’¼åŸÄÊÛ¦Â&¦̈́ʩóTØVÜ r‘`Þã1†Î¸ þi中ù À”ù£ç÷U¸þzð`<”šPÞØà®x2[ ©HÀ»éB€Ù<€V Ioo'ÿú_þ(Û¶®ç×þëpüøÙæß:ÁüØ¿ã?ða>þ½ßF[[úÞ€ €ç£é:ºcO,U+÷òuJ{pã¹W9}à(g†ð÷‹bʹ?C/¦ìôRO –[’‹_A~N†“lfººâmÊÉXJPþ’Ï¥õU–>‘`é»Ü“CÕ``§ÃÕkhg'[L:fCyåìÊ pÖIJ:g>WÆ,O@pÑ#Ûë«I—9Jß‚ÞÛ$ã¼éÜ#¥÷5…—QhK™-,²&v,ôîò#>G~«€öòDÿ‚"¾Þ žÕ9ý7%ìÓ—5 ¼ƒç”pòÆ*Tµ‚7‰à®3ã³yá«eõ…±Ô“Ñ­yÂÉ9C¥ä4!€~ÛÉ7)Ãq]ßò·²~Ý ~ë“ÿ‡¿þô—šåÂññ<¿ò«¿ËƒÇùùŸýÖ­[1+ë’ß÷1t - @¾m8„â~ãUÎî;Ìéדuâ-øûÔ}€â6@%žPx©ŸÒ0”@™åý«l®}¥F0Ë76úîÃES“=…(¸Ü t¾×¦4 ›‚á/׉y3ðÒ 1‘œåjiÅ⧪ù€â+q¥5á´·kDÒÈOܵQUà®§X,Hm6Y¸É¦}¡I,¿|/ìÕ„ ­ÛdÁc\8V&^ }{ßP´/5A„€­ë¨r…â?<ÏÐ×^æÜ«!*oȯb™‚y–Å•$-µûG…-n\ÁBAr“Á‚m6c<òŸ¬‡´ØÑ]ãï[ß×FX5¢ Ì–P`éS ÃÿP{÷g¨™‹¨“ONI6®§jJô7é¬ùHŠñ+£Ÿ­‡t×Zãó‚Ò¥€}¿’#8.CÏ`Ž×³yûb^+tÂÊ&H·›Ô+’Á3u†¹ä÷yø™„áÁ©K%’ÿ2tõÍ^ y8"P–­á,7¨í÷ÂRî4ýC÷šŸ¸S‰0yw2øœ†MQQ+`ÃŬ×\ŠÅò=]ÀWþñE¾úµ—1 ]×1 CŒ‰*ÃÄãñxŒDŸ©VÔÂáKb¹e²3§=bÅs(® cy×QȈ¯7˜¿Õ¦k…E¢MG¿áqååc{]Ü#1¥Ýl…ÕÌÇo€„Ù¸Æ6¥FU௫ô®u¨æ%òZèÏh_"ÈFÓ«h¹µPÐñ¬ÍÒ·&¨—%§þ°„ser¬,¤/jˆóêžð÷Ñé7•¾A-žØh’]©sãS58Óò^%Èyú½Á!‰™+аç ‡½ î9Éø%—dG =. ø³ õ\€¦ V¼?Á³Lõt€¬¨ šðTUÁ8Øe13ÔzŽÅŠq)ÇPM€9õ&IÔÜ<ô\uçI)q]IèÁܘ¦A,óL•ZÍþCRâù”Ïì$ë}§<ÍyÓ‚JVâØëuú·ÚôDåºÁ3uNÿQ cïôý÷LÙàßwª…Z¼c¹EªÝ V‘Œ|º>ù3jEI°_’Ìi!zp𠇀WŠ’“ÍÜ–é ÆzøOIÒ›>šÁw¯Â©Q0®®(\ó¹ú¥*þîÛ»¿AM_1¸ñàÍ4 â¡OnÏóñ¼Ò´‹9öô› “’q=ät»Ÿ %A,løDÛÒ(ŽúœÛ]adoÚ‘ýFؼ2u´h1*WSxí*¤åÚbÓµÁ¦mž‰i dÝŸ[Y²fRNÍš”Sz RÝóÿY‚ž•6éîð‘ÉÝð8ûÅ2¹¿wq®Ïñyž­zC¥ïßÑvà |VŠ×OÔñ j2`cý¢kN„e€™Ôð]…7.±¢7ëBP~ÁçÒcUm£‹pØöÍ Ê¶“®­<þåM,Ds-u¡ü‘pÀÔàœ&à U“4=døiì J©×Æ À)b;öÍ?DRb 0¹·šmƒp³É3hšÀÐ׎Õ8ùßKXWÀ¬ß9¡¤L€»Ú¶[“h¹ª%ÉÐÉ:×÷×éXk±`Slö=:aq‹|Aó£ 6 ,Úç™!+îKFö¸Tú˜× !çÿ. ©¸ !ÿ~×êß´B0Q¥(»à2r0Ó­.(bq‹F…i–Bì„Ì€Ay,À¿Î/⣗~«Bîí>ë,œ¬†‘ŠjÑô`;¡aYOh,zOœ“¯1FÅ}M FmÀÓ€»’Û ý½ï~+–/Šv_×óñ\Ïóð¼×óð\ºë⺮þ¿ëzÔ›ïóñXo±ŠbòÄÞ;[Vêš$õ¤IºËàÔWÊèƒÜ”¿H jÔþÇ…˜‹Œj‹ò"` Œ~ÁüwÇ™·Ñ!;`¢Ï¨‘û;lª"TeÜSnFÞ•Ür4XÃPJ±pa?‹Ï»Å`¢¬7¹¼z ­JŽOV`(˜¢ðÁƒàùÆÂsF$2F®ëºÔë.?¾9Mñºáhpû®Z£V¯K¼0Wh¤7Ú´-3qÒ:^MrcOüg]cSvÃVpO£Åvi€V˜ôqÕ|ì¸Îø—¡ƒa6;8Òr9Ñ`š&'˜tnÜ7ºóÔ-<! ~Qrä?äѯ‚Yĉºï!Ô.­1õovBG×aðL{jøá5&®ù®ùú$Ô4‰Ú©±ü}I*Å€¡¨M_ ‰¤z“ˆ'|À§Ù«§ùý„UÓü®ñû ÜÀ´ùŠ@È0ó­ßj÷ktÚ˜;³Õ¦»s;±dtMÝËlN÷”üd•xéö\â&RM(¼´Âïí²"UÖoÚÑŽþm‘ü§ëXy-š¦£MBÉ5”á¶Hœµý[ås¤GuD´£Ý+n#i¦)|S…}ê³÷<ÅÉ?)¢íVØh7]óIc¦Ÿ¥ú!óŒÅ²w%ˆ¥5}ªˆvx,Ó„xÑÿëšÀ¸¢?ï‘é5ï;€P@æ*%æw1t.¥á%ÜîïïEÂ2R€/%¶€Y‘õ Ê}’¶w[ôl²i_`aŵ²dø¬ËØq—ÊåÍ‚þgbô­²YòTœñ.Áå¬ì;4 KR! R›LnµÑbpòßQåÉ'­÷Z@lTC7ÄŒ &m­³@†£Ù·.ˆ»ÝeiÅßGó–i¤Ö™¤šhŒŸòÈÑ%~c" š+°¢a"wsTT IÐ öî-Ý«Ã^]qôoKÿÚ » §9‡[-‘j\§¯…_TX÷i÷„F¬ å’ !Àt»ÿm/×]ý¦”@R—]›=ÿ&%XK5Ö|(…0rÁeè`Â>yVaB ,_Á©=EøiXçгËáÊseâÕ›§ÒøRQ‰Kˆqçmh±´†WW\9\CM­^FÏ’¸8WD_t‹»H¸qºÎØA­~@, —UhKÃy],Úæ™Øq-¤åV°`«ââÊ*ÿ[™ÄàÜiŽÀ~‹A× ‹ÎU©®°z€‚‘k'>UD}M¯OóÝ êºÄµÉÚÌ[…PÄ Þe„eÇAÙœÕ8Ò´éJZÀ5Í+ž«û*( ¨Âàôub¤Ä“2ìi˜m—$¼¹‚p,Ô¹ÿV"6ªMfÙ° ±KpáoËt-³h_lr¹ ÔÅ–ƒ)°Ú4âßjһɦs™E<£xŠñ«ç¾Z'·×Ã?%‰å&\âÐ;ŽÊN³Åê[âú¯Lí\þÏe좆3×#n”;$é÷˜,ÜâоÀ$– ÁnM’òqË’x›N"­³psŒÂû<Š8™¨äv¤©–"ä˜tRÑÖcòø÷·5«nMáI…i…«ª;‚`š¯T Ê $ïµ)^÷ñÿFbΰ¢R…¥Þö…&ƒÇëˆÁi«‘w,¸"$×5Ož ÜÊ¥ª7T´Ó{*ò8áî?• è~ä¢:àëAÿ£Ú·R cúðÉ×]sP‘Ø9M˜üšºº&¨—ä}ÒÝz—†º0ñw@ÿJ‡µ¯È_÷¹ôB•ñ½.Þ‰ˆÛ¾¥6ÛÉÌrm· Ž×5Dí>ÁWhý‚Uß’"žÔñê’‘KÃGëŒððΨˆù‚eß›d`µÃ¼'cþ¢‹=•hºËläP¸1…š®T¤Ïi7…tB€[‘Ü8é2rÈ¥tÂGs Þc`ög9œ,Pý”×dl‚Øb°îƒ)Î?_áÊç+Xî4%c åŒdÁû㘎`ð…úäqfw(³¯k’AÍWgW]©{¾.,oyï<ùÔÖ]ÚЈuùK»¿<–î¹÷8Â-"|ð})Ã쵦iÍÆ ˜À <ÔˆB†‰:þlµð™Þ+hE(û´Ï3ѳÅD&X¨•.ï©1¶ÇÅ=` ƒé‹IÜöÓüöï®ÐÁ÷§fܱš—v“U|äÐù:§ÿ¢„wL†×ë LÂxßQœ%Ò¿`é6pVé—¦ÑÙ¢ôó ¾Î`þf‹Ž•g>[Æ=ãO.ái‚ü°ÏÁOæá˜ Ë´@"9}±„õóÝ‹mV~ Éþ3yäK,Iš‚ ¯ð]Y\!8¦ÂVìÈË ”¢Ú¡èþ‡E;â\;V£ú¢?+cñlkåjŠÍçœtÕ%Ï•2ÔâÎ^>´i±ìéÇÌö­ë„ÙÕΑÿøÉR]Êé&ݧ@5à¿7_×5j5—3g/rààqNŸºÀÐÐ(•j !‰DŒöö,ýÝ,Z8E‹èëë&•J kZX|Г• Ypo4–TÝ¢,z· b %6aÒíÆ‰:W£B²¦ÍŠü›ñ fqë+xuoáë¨S³g´ï|ÑBn<ô°_ÿ¶DƒüEÿ%I9Š«CxG%W÷ÖXõö$Éåù¯Ôo:~ GÁAl*ýr‹d›a‚YˆJêe‰:®Hæ£ï×ÃįsÎþu™Ìš$3ó¿%Æ…ãe…PÁ5!¨]’TòéƒÅKpþU$B‚L¹Fgé»cÌÛàPÊ\øó2±±Û5”ÞŠ1-஺à¹Ê“šš×ÖÍ»6®ÕVìÚ.:¶oÀšß‹°mP Y,S-@ÉS€î»0… TŒ—_9Äüá_òÜî=ŒŒŒÏº³›¦I[[šE‹ؼi OìÜÌÆ«éíéŒÚ†åƒñ ¢ï1¸{ÕZÿś ½Yî†f е;DÎV4oKjx_”h‡jÞkÓMc¹€ê€ÄÚ¬S9æ“9§ßö±Qµa†¥5ë‚Ü!—à-ŠD¿Á˜Uíè šo²‰¿O ' 5”>PT cç#`ÐsÞŒ;ïtP`C*Ï\y¢Ê’qú×:\ßQ›¨Þh ®*†ŽÖÉŠì»-V¾/ɾÿC‰Pps šÔ®K¼šÄÉh(› †`íÛ’hú„Òž !ÀåC>ê¢Â, ’Ü¡Áà”4®}®JïZ›DÚ`þ;cœÙS"íâNUãÚgjt¬°hïµH·ë¤;Œæ½ð<Å•ã5Îýyíe5-aHã–K-à*ç}Wå=¥:¶­Ý$VïÚ®õîÜLléD"ñ?DJ?é`‚ Tf,WÈ»4™€œÐð„5Mã3Ÿý ÿï/þæ=µ<®™ƒgQIDAT»®Ç©Ó8uúþÇæÍkùз<˳o{’ÞÞÎf¾`®¥aÌÌÛðÖT«0‹@j`ÄDX.¨‰„Ô­.¡eØ rˆ3ŠTQ-4Åq·$}(÷Mj¶Dߥ£·ƒûÙ€x5\$ÓÔî ÕµTQáÕ†£Á”AÍ ¨FÎyŒ¨S:äÃ¥PéïÜMuXqùÕ+ß’ g…ÍÕ'køŸ GŠ ¬#pä7 ÌÿPœöE&šŽD/\öÙãRyÉǾ¹ ª‘Á/‡|Îûu5V—*í¤X³zµXóÄ6­ÿÉ­ÄW,FK%ÂkU5 à犌Jyn ¾#ʽá J¡iW®Üà¿þÆßRùCêoRò–¬½år•Ý»÷ðâ ûX»v9ßõïåýï{+ÝÝíso!€·¦§j4ûÜj÷V lpÚtêeI0Ö‚g:íI•-}=›l¤Çÿm ![íõ¯Uýrý‚š³)73]CÐkþI’Ê`ÀùÏ•îý˜·#.ø®B‹0ö­a—ï+þq÷¬ª ¦î§57 b³"åyñj$‘ž~½7ö0º ÐbOr(æ…·º³­\¹˜úǰha.§sÄÚ2 “P(ÌÙ³=8x”W^ÝÅÖ­{8Þr MK1M“»šh:p”k¯YËGÿá.æÔͼ0%Di"I÷ùosàÀQ>ùñ÷——sÞ!A\ápÂDvÜ:LÈ«—P/(mpq¦)‚yhÏAF²ßÆ5&H£ëEÂÑ‹nYÃ|Üî”ó2CÿÞÙ³ªø·/}”M×]Î~ò;6?ûº>˜0|ú™Wðx||ó?>K]]Õ¹i ͰOê½Ö?Im¶ÒjÉŒ¤JÔE ³Þ‘FÞ ûÿàEì1“2õBÇqð‹bו‰i¿“„.%‘rIÖz ¤ä¨h!“Î=zÿÁ}æâÍ©#0„Ä9͆bÚtTCŒ}mb‹>j—y–(Kö'yuÖ”ŸCúî±rIzTrâµ ¾ƒ:jpbÚsüå bÒ©h´QéÑL2Ý™ÔÍ©gÖÊQtÙ¢‰kõS!0|AúûÂ$”€/Èî“ ÇA†“RTUeúô¢ ú°™±N½åËPWWÍÿ>ø?üÑoi;u&ñž×·ïãSŸùßýÏ/0kVå¹ •p1!‡j(Ú0%!»‰Ql T–-wR\ï é}øÿ¢Y³õ†]‹ø„à 5«^ó2AÝ»Óɯ° çÒ´DIòË´–Ù8ùÝ©i¯½ÐÐ’¼;ZØ$Üb0šŒ«¥F,‘Åà¨UÉ[`'/ÖÏïpYUÓM´°L\gÇYAß#8£ûX ÁØ¿aŤ[è´šQzuC¦:Ó˜YSìKÄ´UKp×V ¤§%ô$åùxoB€>à¥oÀïá)ÅÄ€àNMÁf³%JxŠ"p:“ú‘s…a¤¥¹yï{naáÂ:îýúÙ²e{âõ]»ð¹þ6ßÿî™>½xò9 BÄË€½×*€ØRlWªÔ;ɯs’‘oCJIç‘('ÿÀ|Í$%:õ N—s¥`ÞG2IIWhÛ¦·1‚…œy¦/pQ¶0…t|¿Œœs{íE… FäÕ:8­a´ÉdywiU>3VØq­WÉŸí #߆ÝiS4lÒÓ¥çP”þ½Q"†E&b£TbbÿjФGè´É(]ºŽÝ|&ë–/ÓW/%½®%+”s­ÕOZ߀ìöú™Øœ3&U”Ò$/7·ÛE(dñ5 Ã$ OêGÎñ²ß³ùÁ÷þ•{¿öcþøç'9‰W_ÛÍW¿ö#¾õ"==ur%Â!9€ÉT¤ Å3×8 øzuN¼¤{[„ÈnW)D|VýÜ÷d`³ öür€àf‡×:¶§4|ïИ{se«RØ÷LçÉ©=¦7MHÒ–ÛI/°Ñº%ˆ}ˆBª]0ïÚ ›õB$dÒÙ¡÷PÏ^èaµgä<ÙÆ¡ I¿bpJF9£k :(/.cÓÒ…”¯i s^-jnö°²Ý›'qéé7z‚a#K€S›´ÆÒIiš’‚‚\ óÉ9Ó49uúÌ”'˜ ä°0¯üû'p8íÜÿÛ‡‹ýáGžcö¬*>öÑwOîË`2ïµjüA¯A÷Ñ(=»#÷é(à|èÎ 1­:C±Rùvst¡‚°Ë¤ðm.Òómì½oíq}°Ë°ù}F9S¦x¶‹”¹6Œ“Ƥ“¦)‰Î’̺ÖM _gà ·)†µóZijpÐÀÓ¢Ñs ÊÀ> ý¨‰­Ïj¥««rhÙÎÊàk´ëQ i£$¿ˆ çS±fÙ‹ê°æMIÙî‚BJB=}‘ÎHÔ‹µû_ ý›ñLgVVsêª9xp ØØx˜H$ŠÍv:DÆ€išdf¦ó…ϯ×Ï_~6ñ÷Ÿþü,]ZϪ•‹'ÌÈI! ÒoÒøk/Z³‰8%±-)l¡ð†TqC/ÖÐ ×l•ÌJ¦ ý¯FqL.iI˜-([‘BËK"Ïè¸M%ÙÅUÀÕ'8ój„’9.2jlôÚtTýÍaâôç¨jbÌÌ|:YÅvÿìE=.G0{NF ýÙ``†~Üí•$š2Æ¢7øb7§(a]¡0'Ÿ•õs©^»ŒÜÅõ8¦‚Ã>¥e»±×΀˜®À$~[ê:Áî¾pi^pL0ܰ €DJv;«W-áÁ¿<“ˆ¹÷ì=H[[ÕÕåS> Ä4M²³3ùÂç?Lkk;{ö ··Ÿüð·Ì[CFzÚø¡@Œ—;¡€€ÔN›¸¥°úÞˆ*nìsQÕD/Çl•‚%ŽDlksXõÀî†þËztЈÚLòֺУ’ÎGøãϰ!Ò ûMÒJlt¹@\$Jÿ˜ç£?kiÖ¨î¬NÊפY`çä® žG¢¤ÉãÐì¦ ü †1p˜bÌE»MHÅä §Œ~ r3rX\7›™k–‘¿lβ„ËyqÊvÃ_ôѨÆéÓg9xè§OŸ¥²r:«V.™pó”Q ÷Àqÿ!ÙŒPÕÀ4c½¦4¹lÅ"f̘FKË)ÚÛ;yêé—ù‡êò‹r1MÓdÆŒR>óé{øðG¾„×k=á/½¼ƒ§Ÿ~™Ûß~ÝøV5f&³ÛâÅá ÁA—Ì1ɹÃÉ´E.2 m8œ š& ôëDüi¹6ò¦;È^çÄßµZL%èùP8ßIç¾ê ÆÖ2 »%A+KEºß9c¬Ê3Ñ|ÁÉž¯p‚6ÒçÚ)ç ·ÂŽ;CEšpbGÖŸpw £T)H‹¨Ö!Œ±èBŠI§Ð8eFñh’ÌÔ fÕÔS»²¢Ë‘R9‘›”t1ËvñóPU4ÍZô;vîçÅ-¯³}{#íD£i|ï¿þ…M×­Ûs3f Ïãó € ¢ÇX€ ˜:fÌ0MIii×]»ŽÿþÁý‰7ÿáO³iÓåÌ(ŸvQÆ„†ÁºµËxÛMWòëß<Xs÷À£\uå*23ÓÇñâà"»ÆD¦`Æj7i™*Þ“Í!úvG6p.SYôÁ,rjíxR"8C‚½RÁ£rt_{tìZ¹Yä£Ô\œç¶Ž¥j xQ&“ÏoÄÙˆCÖ²4aZ½‹¢9.ÜéÖ¨i’ÞÓm/ñ>©á0šœ@D‘ô6¥W7Hu¥Q5³–—-¡då’sj±½Ð°\{išhšÎ“O½Ä“O½ÄŽœ:ufEÞëõóècϳñªUcwz á àéó „u. ÆOZ–ÆL BQÞ~Ûµ<üÈs´µupäÈ îûÅŸøÒ?ŠÍ¦\Ö™ÃaãÎwÞÀOn¡»»€Ý{ðÚÖ=\wíº1½€Éæ¦@‰׃´þOÛpDî˜9ò¿bÐ{uWFL#`‘eR«íº$Új’2Þª€FX"âã«ÎåøìÖ¼;=hZÙxƒ#Ò´0§8)IóX?*¸ÒTtÍÄÓ©Ós$Jßžø'%¶kúmè¬j&W:Ö$]c {QŒ‚[lÇ™b¹ñÝíQkºf›Éá¯ú°÷ÄÆŸOP9šÁ÷(DéÐ5 ¬²ÝúEVÙ.kAöÂÜKV¶Sa˜ttt³{ÏA^xqÛ¶íådk{‚·bÅBTU%Ž Å(ìúà¼ï½·Æx-&áp”¬¬tŠ‹ p¹œãzÍ‘ž>ãl0ÜÏp`2ÃAM™$ .¥$//‹úìßqüø)N¶hº§NŸå³Ÿû&ßþæ?±rÅ"Œ)vŤ”Ì™3“ÌÌ úc]Š]8xŒŠŠRFýyO(¿9Êc‰Ã2Á̄ԕSÏ…°Gb¥"8R">en9vk"N$`""ÄçIaê›[±î¶ÆC Ù$Y56 MnM¦åJ,}¾wgâJUp¤X‹pРóx„žƒQúvFQ»­EžæS­äãÌýX1½Œ•íÎÄÊvC¡ §€óæRµz¹Kæâ(‰•í.ò¢B (Jl¦`ÓQ~õëc‹¾#IÇÂét0£|+V,DA(A‹j(ŠBvv 1 UUéêêåÄÉÓ,]ZÍf;q-MBÝý‘žHt€ 4 h8&©”ü{†a²xѾðÏⳟûV"ìX+ÿä½|ùKåêkb²`Sãš™¦¤ ?‡¼Üì„Ð4¦¦#\wíÚ1N(6³`JŽèü ¤ÅIO[mEàݦ‘*­:¿p€ê}Ò’0›À—npe(xÚtDÌ‹QÐÂW¦‚éÂ*•t!A+…¢E.<§5Œ“É´\¨6AzŽJ8`r¦9LOSŒ˜sÌÄÖ]LdM¦1½¼N¡qÚŒâ×!73‡…u³™¹zyËàœ^|IÊvñEogÏö°{ÏŽ;ÉûÞs+/¼°ûûp⽇ʊé¬X±Ë×-cÁüÙäççXãÌÃ¢šŽªª¸Ý)±R ¤««—Ÿüìœ8qš™Õå¬_·|Üõ!uƒ@woø¬iÕ¸¨@JÓ¢ ÷?MSrã èéõpï×~œ ·¶¶óÉO}cßÊ{î¾…ÌÌ´) Üniéµ´œBÓô13«qàR=ãcÒ´ØjÑlIʕ׻9úHûɘ…ŠgÖ'Ë? ä œ*þÓalZ¬Œ‚È€AN…Q*0{­¡ÀŠ×ƒé&…·¹È*±±÷×^Ãh¹áÉéƒazöEðîÓ0Z$ökІsŽCoº„Îi3Š×0IOͤ¶v>5+—R¸"V¶s».zÙnè¢ïêêeÏÞƒ<ÿ¶˜bUU•e¼û]oK<ß©©nn¼á 6^µŠ… ê((ÈM’¶Âj_×u›MEJÉ®]Ml~î5ž~æe›q§¸ø‡ÜEYYɸ•3Õôôûû-Q²áR`¥`T EQxïÝ7£k:ßüö}ƒÖöÒïñòõoüŒ]»ðñÝͼúÚ)µBñ½Ý=}D"n÷(±•œðyr@0ÝÀU¡’› \Pé 6QÒZÄÞ)¬c4-2Í#"ãHœ•*ªüGõD¯†ÁסS:ÏEñ&íAìýÓ²BPrc UkR9{8‚ÿËûHŒ¿Ð÷xÿ_¢Ø¼–€‰ˆË”MbÑGc7§e„^ÃÄíJ£ª²–ÚË–P¼r îš(i—¦lgý¦¤»»}ûšyaËë¼öÚnZNœ"T'IKOÅfW Äží3¦ñÅ/|„üü ÃHÄöVŽÀzà‚Á0¦abš&÷~íÇtvõÐ×gy©E…y|ê“ïã–›72î3ÆÓÓïëÔ¸`mÀqLh0Çž (¥Äf³ñ{nÇíNá?¾ùÓĉ†Á“O½DÓ£üü§_eñ¢9܈D"„†5$ƒ¡˜†€k”ë$'%Ñ?U¦¤p¾“¬*;y3¬ÉÀ¡KRÒU¿#“–ò íÿ$u@AF@KéŠEì Žþ½ˆ:%% „<&Ñc†•qlº`à€†v…¤re*©6¼m®l•Ü™2rmô‰rü~?)]É»¿*Y]êàLbÑ[7:§e”ÃÀáH¡¼¬–UËSºjéPÆ}cŽ !†ÂüðG¿å±Ç^àÄÉÓI‹Þn·QVVÂòe ¸~Ó夺S›[ŠË‰Ýníøªª iV °íÔ/šƒÍæ&YJE†É¡æãIÇQVVÂÚµ 8öñy3B`øü ô Du.(&$êJc¼GJl6•wßu¹|õÞrìx[âuUQÈÉμàüEtvõÒÕ=|8ÉØBš2öò%ó$VZ:!¿Á™CzF ž2p(T_JÙ’:ç…1_% !A^µ‘#½Œzn¦ ²JPXï¤cWµ“ÄIªBØiÐq Bù‚¦Íq1m®Ë¢ h’öƒaŽßïǾgŒé6ÄôCõò:ˆÒ©ë 8˜>m‹—.¤luõµ@÷ü ª †aâñxIµ´ø}zh3Ç[Ú’Þït:øÂç?Ì ×¯§°0›Í†®ë‰Î×”ªªÒÞÞÉ®ÝM¼ðÂ6^yu…yüî7ß&-ÍM0Jˆ™Õ3(//a÷žƒtw÷±}G#ÿþ•ð½ïþ éicw±Z:>º|¦¨“Êĉ@c¡â'qí5k)›^Ì׿ñSž}î5¤”¬ZµøÜzö' Ák¯íN’HMMÁn³®ƒu©«ú;4Z_ âÛ§A5^U稄ùïÌ ­ÆFà 5"ðŸÒ™¾0WŠqtd‡Ÿr™^çÂæt¿Á1”1( ¥WáÄOý„ßn[í@šà;£Ó³+BðµÑ\Œs IzyíD9ch˜¦’ÂbÖ-ZÀŒÕ d-¬Ã–Ÿ ¶ ¤Œ{¶nÛË£=OSÓQþýß>Æ‚³‘R&Êtåe%¬Zµ]Óù㟟 %ÅŪUK(--F×u ÃÀ0̄ޙ³Ýüó¿ü';vì§µm°XT”‹ùI„ EEùüè‡_fö¬Jøý£|þ ß!ÕØ¼ùUž{î5n~ÛÆq¨ë‚h¯Göú&¥0e²à‰€„¢Œ+Ïmš&õõ5üàûÿÊÿüêA~÷À£l¸båø¥Žó9h›JóáîÿÝÃ#Ž¥¨(§Ó1î™p(ÈA(‚öA<¿Œà–ê  ¨ .CàÛ«½ÙÄ‘©àS$vCàmÖ‘›$Ekœœx%@šwP9XH Ç•ŠÕ©´maî3Q†5 (ÜǺþ3ę̕0ô=NΙ$+/^¶;‹Fû²Ýòys©Z³Œœ%õ8J À~qËvÖ4jÓ4Ðu3A°éïàKÿö}öì=XB2 Ô!¥$=-•/ÿÛǨ­© ¼|¿¹ÿ!þøç'°ÛmØbI½8LÓL$k娱ÖÄk)).ªªÊ¸þúõÖs'%Á€«¥¦º),ÌŦªÜxÃ}ì^Üò:QMãß?ÆW\6®éé×φ¦F މ €u0L“¶“íäåe'øö£íê†aµücw³qãj¦M+<ïÝhû$X=Žðúö}üÇ7~JssˈÏÌ3sƒsisÖ{lüTH€ `iäÇåË„ôwhL«O¡ÿf ÏŸ¢¸ü–ˆ¤KRÖÛ˜ó®tý:íÿÂ}F àŠ*Ð5ä÷ÎE/oHÙ.'3‡ùu³©^µŒüeó-‘Lç¥é¶B¡;w6ñâ–×¹êªU¬X¾9z‚£ÇN&Þ»}G#w½ë&TU!==•W­¶dâA `íÚ»‡Óžôý†a âº\NªªÊXµr1—¯[μúZrr2+™ÿ.—ËÝfÔ’ÌÌtÞ}×Ml{}/áp„í;ö±uë®Þ¸fôgÕ4 w÷F»-À׈c’Ó%º¦óõoü„Þ^7ݸuk—QZZ4êt_«"Þð@^ÜÆÉ“í¸SSТրÑ}ͼ¾}_"Ù8¹9Y,_¾`Ükt©«ãå'„ „*ˆzbt\ì]‚¶Bä¼ËAÝÍéœ*Ñ·_Cu@ñ<Óê]„ü&éÃydœnÁa¿/Ç9©l7`˜d¤fP3³žšU VÙ®ªì¢—íâ%»¡›¢¨ìعŸ÷àŸ1Mɦë.O\Ñí;ñû3§û›ŽÐÓÓGQQ>RÊÄÂBMìðv» ‡Ã>ä ‰X`½¾¬a>ÿØ»™W?‹üüì˜ç1øü›¦L„î”TUýÝdíÚ.»lÏ?¿•P(ÂïÿðkV/ÅårŒˆX¥aèî uY€ ®Ç9•ü¼òê.^Ûº›3J¹bý 6]w9óêg‘–æŽéþ߉ûUUeÿþÃ|õk?FQãT#ذá2fÏG-8.nú&c‚¥>äš¡¢ÚÁ6UZ;´ÃøžÔ8^ ze*Õ+S1VÈÄBï>åÈoü(¯ÉóÖÆZ¶ë:§be»g*•3çpÅŠ¥L[ui»í</ûÓÙÙË ×¯Çá°#>|¯×OmMÓ¦"¥5ÉjÛ¶½äåfÓÓÛÏéÓg9|ä%%…I»®”2±ÃÛvv{Ò³¦iáX•`éÒz®Þ¸MÓ‡ë*šæ ’âJp2ÒÓ¸ëÎÙ³ç ÅÅùÔÍ®ŽU¬œ _×2Å×ÓèBLJ4ÖIV®¥å--§øãŸž`é’z6]w9k×,eÚ´Â…Ò|Ùÿú¹µ¸\ÎóFCÍÌ|øCïŒMÒê›g4N &„3$«\„<‘!e<¸û:~"pÊ ¸Á…#U0éÚÁólg›ÀvŽòÌñwÇÇ\ŽuÛÙì)Ì(«æ²†ÅL_Ó@Z]5jvÆE-ÛÅS¢{SUxmëþý«?äàÁ£”M/áòuËÈËËFׄ›_QQJVVBÀ©Óg8pà(™™éÜzëÕüêW …Ùµ«‰Ë×-Kú=ËX;¼ÓéHT âˆj:ѨEýMq¹0 3áåZ‰‰ÇãÃá°cwØ%C·Û•$øaš&ëÖ6pÿ¯¿Eyy ùy9Iç9ä`†Âx{ú½SÈ€sI +¦¯×ÏsÏoå…_§²Òê|ºîڵ̙SCª;e„W0YH)©¬œN~~§†È‚†Y³*ùÚW>EÝìê ¼ë¦]ª$àPħúHB™’œ·;˜VïäÈ3Ô’5ò¤ö*ï×h~$ .AK 0ÕP&Ó /ÛµÉÄÁôâé\µd3Ö,#cÞ,lyCD2/RÙÎ’žô÷{‰D¢ä&^ÛòÒvíj ¯€ÞÞ~ rñûƒµÝÖÖTàt:B°wï!Îvö°`þl6]w9<ú]ìØÕD0NR´j\N6ÛùB Eµ„p§¦`S­]Ý3àåÐÁã¼øÒëìØ±Ÿ/þËG˜S7“@, èv†ñßq»],k˜7þäk!0¼ú¦˜“âH)M§ÃÁç>ûAfϪâég^á豓IãÀLÓLdHøý£4,ǦM—³võRŠŠòQqN^4Mòós¨˜Q:ªBPT”ÇÆ«VsÏûo£ffÅ„!GŒ]{iÓ’ø6RLÌbH™k£r¥“Òy)ôuht=Æ­²¨8¤ÀÙ/„@9‰„Æhe»C#j¨ç²vþ<*×,#{ñÔͶ› „ø|þçWòÄ“[X¼x_ùò'B‰DiB¨ñùœ9ÛÍœ93éïàôé³ÔÖVÆÄGt¶ÆÜÿùóg1§®šªÊ2::ºhn>N{G'ÕUƒ2vRJ‚qàrÆíàƒjDc!€i˜l{}/Ï¿¸-[¶sèÐq|þ©©)(B`Åżãí×qÇ›FôÃX—uüçtpÀÔr`RªÀHL+{>gÎLêêªyï{oå•WvòÈ£ÏñúöF<oÒç</Ïl~…çžßÊÌ™å\uå*®¹z u³«ã¾'J ÊØÍxÿûn¥²b:^š¦ãt:((ÈeVm‹Ρªª ›Í6¹|C"p‰ Áž¯à¸ÉFÁB'¹ÕÒsm¨ª ëd„æŸûp¶ˆqyr‚$ŒÝmÔYy4ÌCõšeä6ÌÃYZ4å³í&‹`0Äþô8Ç·‘‘‘F4ª‘’⢿ߛDØ ‡#œ:uEQè8ÓEOO™™éTU•‚žÞ~vï>@ÃÒydd¤3Þ,^~e']]½8p”š™3£¦)afÜÄO! ªiDcÝ¿øåŸùþ~ÃÀÀ Þš¢X„ŸÌÌtœN_þÒÇHOOüs9ÊŒözd—?8¥ßȸ?™âÂ<Þ~Ûµlºîršå‰'^ä™Í¯p¼åTÒ †Ass ÍÍ-ÜÿÛ‡Y¾l>7\«V.¦°0× =5¼æêµ‰RI<î²ÙÔXžÁÊŸËE¶<€Kc¤)©Zî¦ze*6› 1ñœÑèØ¦÷É®““'äŒûŸ`¬Û®ÍŒâÓ$ÙéYÔÏZDÍêe,_ˆkÆ4DÊEšm?¶ ;C%i”püxÝ=}ARSS8ÝÞÉ™3ݤ¦ºQU¯×Okk;RJNœ8ߤ¶¦‚’â„€ææNœísÅ%æ(–Uõ´kô4[2YÑC¶NHÕ'ËEÒ˜+E§ÍˆÒ§¤§¤S]]Ǭ•K)^¹wu9"ÍÍÅšmg%Ȭ£ ‡#hšNJŠ+QÆ)ÁétRZZ@_ß~ŠŠò9zô$^¯Ÿe óQÁÖm{imë ‰&H9ñ iš¼þú>ÂáËæ“âròôÓ/óÌæWˆ¯›={0àõ‘™‘N¼W?ó¬cú„B¡0‘H!ÓJ Y²d.W¬_AÃÒy”–ãtÚ›‘u.op}š&¡îÞèÙ¨6¥[À4À4M9fà>4ž)--äÎwÞÈ7l`_c3?±…gŸ{•“'Û“.ˆ®ë8xŒñ›ûbåe‹¸~ÓzV¬XHA~®¯Na¦ù’zØäèæÑãæèSmÎañ/ÛµÉ(ݺŽÓᦪª–Ë—/¡tõRÒfU¡d¦ÇfÛÉ)/Û ]ô^¯ŸæÃ'xõÕ]ìÚÝD¿—… ëøÐßÝÁ´i£&†m6•²²üþÝ=}ÔÔÌàà!kÅ‚ù³Ñu+¾oïèÄãñqì¸ejk*q¹ø|A^ß¾€¶¶Þ{Ïç8z´5‘ä8ÞrŠ“'ÛY¸`6–4è¸S\Écʰ$ñ7]w9+W.fŲLŸ^ŒËé°~L3)v! ƒ`w_¨wŠ90$˜¦f¥ñ'ü"놸Ý)¬¼l1Ë—-à÷¼_ÜÆ£½Àî=ÙÑ8z{=<òèó<ùÔKÌšUÉ5×põÆ5ÔÔÌÀétN*WpnWö帺¿Ұç¿èãzy}ŠÁ)¡ÓÐQT'åÓf°|é"ÊÖ4Q_ƒš“uIfÛƒ!š·ðÒË;زeMŠ•·ïh¤»»ï|ës¸Ý)#î­7_Q¬]·««—P(’HÖ××àóèîî£íTGBœ¶vVªªÒÚÖž`ˆÆ•wÀðHIq10ࣿ€½û±hA Ñ 3á8]ΤpÅ0LæÍ›Å÷þë 8clGÓ4G¨ü^HÈHowŸ¿{ä,˜â*ÀÐÔÖ`ó\¢¡äˆåÓxï{nå–›7²{ÏA{ü^xa[Ò´_°d½÷ï?ÂþýGøÕo½‚åË—— ¼1RуK¨ó^lØÍØ>E¯ É€bp*¦—gJ;¥…%\×Ë[X‡­ ÷’ζS…?þé ¾þŸ&-úáxò©—¸íÖkØpÅe#(°R´i…¤¤¸BV‚¯·Ÿ'N“–æ¦fæ zû<Øí6|45¡³«—ŒŒ4ª*ËØ½û=½ý@Ì£˜^²eó¹êÊUô÷ð¹þ6ѨƎ¼ë7 ª*º¦‰DÉÎÊ ºªlD¾Ân·–É…ìgû¦[:Þ^Ï7ȸ4€5ÄŒyç¾oÆmZZ*ëÖ.cÕÊÅœlmç¹ç^ãñ'^dï¾æ$×  ««—‡þº™ÇŸx‘9u3¹öšµ\yåJfV—ãp8Þ°WpÉ{&y|CËv§u°©Pœ“ÏêùõT_‚Æ›A÷>Τ#îE{Ggbñggg2wÎL–5Ì';;ƒÿ}ð)öì=D(æ•WvrÅúå#~CJkeFF:@ˆ3gº9yâ4]½LŸ^Ì´iE8]N22Òðzý¼¶u>f”OcZI!‘H”­[÷ ¥dÑÂ:>øwï`ÉâzŠ‹ p:´´´QX˜Ç©SgسçÝÝ}‰×>õÉ÷1sfyRyðÒ<–@_ÿ€g`JH@0QP)Í8p0;šh˜ÔbŒ{Bª*˨®*ãö·_ÇŽûyì±çÙòÒÚ;:“>jìÙ{={ò‹_þ™U«–pÃõëYÖ0ŸœK_à\oÔTé¾Q -Ûùƒ4ÚŒ( ò3rX<§Žš5Ë(h˜ã"޹²ºì¬º‰ßàØñ6öì=ÈéÓg™1£”k¯^Cvvf’» PŸÃ÷¿÷¯,k˜GZªÕ¦R[[É{ßÿ9|¾Gµ‰h‰5)%ÙY™äçesæL48B8¡ª²ŒÌLKM7??‡Þ^/½¼ƒH$JEE)ÙÙtvö°gŸ52îŽw\ÏÛo»MÓ0M‰¦iæ2«¶ÇKuuY¬%X’žžÊ-7_rjóO“½æšÇKÿä8S¢ œ¬„±•Ð4×^݉"¹¹Ùdee–æ&%Å…ÝnCQ”ÍVŽc â‹6##+7¬dÝÚŽ·œâ™Í¯ðÄ“[8pàèêïÙÎþ÷Á§xìñ˜;g&×^»Ž+¯¸Œªª2ìvû佂x[ó›ÄÞm×jFÐ$™î êæÌcÖêŠ.aãM(æØñV^yu7/¾¸ÆýGèëó$^ߺu7_¿÷Ó¤Çf2ê±ÞøÔ47sêªIMuc˜&¦f)8——•Ðtà(ýýD¢Qûˆ&²ÔÔŠK hܘööNDŒQ7» §ÓNzz*¥ÓŠhnnI …©­©$%ÅŃÇ8uê ÙÙ™,X`% ‡&Ÿüø{BP[[‰ÛíJ¼~1¤ì'yåÑz=²3™0%,@˜Ø@JÓÄ”„#þã›?£©ééé©dd¤‘“E~~EEyåST”Oaa.y¹Ùdçd’–æÆ’‚ÝnCUÕÄ\‹¸q°TSTjk*¨­©àÎ;®gûöFyì9^~e=IÇGع«‰»š¸ï¾?±vm×oZÏ’ÅsÉÎΟ^ù&ARÙNè´šQztƒTg*3«ç2û²Kßx£( {öä›ßú9{÷!ºb“ä/mfÍꥼãöMÖH¬˜È†ªXIÎø½RâNq‘““XÄcŒÌ¹Ãagzi1'[ÛéììEQfÏ®FaIpÏ(M:ÖY³*âѨÆüye±Qu#Ÿ…%Kê‚7ñ³" ÷ôë݃:Ã=€‹Ü `Õ:,7^7ˆF5z{=ôöz8qâtÒû…8vÜnééiääd’Ÿ—CaaÜ@äQ\œO^^ÙY¤§§âv[ÂfSÉÏÏáúë×så•+9zô$O>ýO<¹…C‡Ž'¸Øqtœéâ÷xŒ¿>ü,óêk¹îÚuÜxãŠ óÆ¾±Ò’×¾Øûÿð²]«´¤³œ6eÕ¬]¾˜²5 ¤×U£de\” þx/Š"hnná¹ç·&þ–žžJuu9ÓJ h:pŒ“'O'toºñJ{BGµÙFˆµ !°;¬>{E(ÃJmƒP…òX)°·×ƒ”’ÜÜ,ª«Ê¦DU*+§'ÞŸ‘‘JUU—mÛöât:Xùr22ÒÆå•¼iaJB]½‘³ƒ³¦,“Ș±*€ÇOÀµ—’H$J$¥¿ß›(Ñ …e RHKs“I^n6……¹åS\œOQaùù¹äåes÷]oãÖ[®f÷îƒ<ñä‹lݺ‡®î¾¤› …y}û>^ß¾´47ï¾ë¦1ë²rÄÿN†—íÚd¬ñFqP^3\•(¾8]#;î†^¸éÓ‹±Ûm ƒ2mZÅÅù˜R¢"˜Q> ‡ÃN4ª‘ŸŸË´’Büþ×^»–/|þCÌ›7ë"Üá©Ôu‚=}¡)½ zSÖ5‰Àjr8ÌœYNÓ#I¯+Š‚ÝnKRUÑXg•ÇãM4q …ÝnÃår’ž–JVVùùÙPT”ÏëW°{ÏAŽ=1ÂÅ[3'8™)ÝýG+ÛÒ5 ÓÆ´¼B6,œGÕÚed/ºt7Bž~úe~}ÿCìÛ×Ì?|ä]üý‡ß™´Ž#^òr:ÜzóFV¬X[’%‹ç²tI=?ñ"žÏÅÅù‰kQµEÒo†‘h“ÍÉÎÿÞ"IIq©î<±ŠÂÌêòÄŽnš’Òi…å“‘‘Æ;Þ~-YYé(ŠÊG>|çVô¿A ¬Y¾î>ïÈqà—&`JiJÝÀââ+ÿþ ʦó?¿þKbMU¹rÃJV,_HOo?§N¡ãL]]½x<^‚ÁáptÒ®—¦éhšŽÏ°xׇ_SªÚFíâñḈËB_À 8¼lwš(mºFXfç²jÞ\jÖ,'oi=ŽKÜx#„  ñ³ûþÈ˯ìॗwðž»o¡J£ºˆõlÄ•ÝnO¸éѨF(±’€±Þ¦Ž ÁP¢Dhíà£ßG)%yùÙdeg& @]]5v»=Öb2}z1?ùÑ¿3£¼$‘Wˆö¢Ôê§îaB ôy|^k†ÓpÐÇ$<!@A~ÿôÙ¿cñâ¹|ëÛ÷±¿éQMã…·‘••ÁÇþá.Ê˧ †ðzýx|ôööÓÙÙC{{íœíì¡««—žž~¼^@MÓ&e±-¾µ6êkB\.çøçbÊ r%ã‹ÞŒ•íÚÑh5¢x£’œ´,ΛÅìÕË(X±WEéEk¼™¨áFÁéÓg9rôdâoMMG8ÙÚÎìYUI“ã–P¶aÍ1BX™~ˆ%tc µ¤`ˆ (‚ÎÎ^ººûP…úØ ˜Ñú9¤„ÌŒtòó²9yò4éi©ÌS“ôzJŠ‹¥K澉yç !0|úû<Ñ)Öˆc29‰ï”(ŠÂ5W¯aVm%ßýþ¯yð/O„øíïf×®&>ý©÷qÍÕk())¤¸¸Àà$²þ𦠅ñùôõyèNÚ;:éìì¡»»Þ>>o€P8œp-ǃ¢LÎòü¯äÐn»3ŠÆ #J_Ä Ý•ÎìY³˜³ª’•KpÏœH¿87VùÕ*Ùy½~rr³F×÷]£¸.^]Ý}ìܹŸºÙÕIïÇëB5>–hèµ´þ±©*6»-‰o³©%YTcÇŽFúû˜^Zãà-1Ÿ’âdÅŠ…ÔÖVrõÆÕ44Ìa,ÞþÉ{y}{#ÿáwR:­p„Kf³©dd¤‘™™ÆôéEC䟬¸3ÕDO¯‡®®^Ë@ttÒÞnˆžž~ú=^ü¾áH$ñ0 '–Œ¼d’sõ†–í:F”®¨ŽËžBMy W¯XBÙꥤÅ3ø¡ñ&¾è#‘mmgغmÏ<û*Y™é|ëÿµ ]7ؾ£Ã0)/ŸFOO@ˆ—_ÙÉío¿.I¾*~ßTeäøµ¸æXýói©nLS&›͖0B6›ã-müþ°ví² gâ)ŠÂ§>ñÞ„<×ßlLÎDû<²Çð0²x «£ì`¦iâr9y×72¯¾–o~û>ž}î5 ?¿ïìÞs€Ï~æÖ¬^:¢tPàsä99vœN99YTTLOpLÓŠïÂáÁ`ÇKOo?gÏvÓÑÑEO¯‡’’Æ““RyöÑw³paßüÖÏÙ±s?ýý|û;¿`÷îüãgîaÑ‚º¸ÐèypÜs8¯ïÝ”˜ñÉ:$‹dž ÊI-JÈP(ÍÊeýÒ9Ô®]F~Ã|œÓ‹Á9µüxÃÍÐ>EQøëÃÏòõÿø)gÎv'8UU©™9ƒU+³nÝ2.˜äÆûvöì=ˆ×ëgN]553g°¬a>¿¹ÿ¯èºÁ˯ìäúMëc‰ÄÁWQuhBOáìÙn޵úð—5ÌÇétG`Ó÷N|&++ƒ¿ÿð¼ï}·Œ)ò,€¿«7Ô)¥‘ À‹$?`F¥4&ãÚÆo꺵 Ìœ9ƒþè·<ðÀ£‚!ž{~+͇[øè?ÜÅí·]Kjªûâ3²bÔÔ°)1U«ñÆ£´å¸Å‘¤e±|ÎBæ¬YFÑe‹¬ ~¬o}*3øªª ë]½ôôô‘ârQVVŒÝnéÞŸlmO4KÅUoÃᥥEüô'_¡¶¦bÂúw4eÇŽFæÎ­!++ƒyõµæZj¹;öÓÝÓOaA.0HÚQ”A~E<_óÄ“[8uú EEù¬^½$Ñó¯Ä¥ÛŠ‹óYyÙ"Þqû&V,_ÓÚ{kñ !á(þž~ÿÈY—¤ ó0ˆÍ:×¢Z,6“cÆÚ†aRR\À—¾øQ–,®çÛÿù Ž=I{{'_ü×ï²cÇ~>õÉ÷Q3³üâgrM“~]²Ï¦W7è ä8S©Ÿ9—ù«–Rºz)©³*ñ™õSœÁÂj©Ý¾c?úólݺ‡îž>œ7¿í*>ý©÷“‘‘†Ëi¢Z½j —¯[Æox„Í›_åJ2c×}ìãTÁÙ³}ìo²H\K×ãpØ)))`æÌtttq²µ¦¦#oX™TOW»Í†ª*x<>{ü~ô“kìÌêò„.£a˜äåeÓ°t—¯[Îe+2c†ÅÚÊ#x £Ã†èïí÷z9SºøaâÀR•’Þ>ßúæÏ¨Ÿ[Ëë—SRRˆ¢Œ.êiš&v»ÊÍ7]I]]5ÿù_¿äÑÇ^@ÓtüËÓ48Êg>õ>®¹zíÄsÒ/bt=!I)äòòJñ¶‹-:n} Jvæ”sð‡Ï¨×4ÿùõ_øþÿ&ÑÙÇOþGjj+¸óޏù櫸åæ[òê<ú¼õñóšðwšŸàôé³dd¤QX˜ËOnaó³¯r襶GxõÕ]\±~Bôø}•Ð|¸…§Ÿy™§ž~™»š‡#̪­äýï½5‘¥w8ì|òï¡lz UUÓq¹\ õœÿKvB`x}ô÷{=ÆEâÀd @LL× ^ܲ_ÿæ!jj*ØxÕ*®»vsêªq¹\£ÌCšÔÖTðo}ž%‹çòƒþ–3g»9|¸…O|ê^vìÜÏG>|çˆQMSÓ4©v8ùÀGï3nÞˆšŸsQ8øªªZ=nwJâï¿ÿããÜûµG°Ùläæfá÷ ‚†Á³Ï¾Æ­7_Mé´"ÀòºtÝHL1I}3)%»víOеÿõKßãÌÙîôí­ÛöâñxÉÎÎLÜ®î^>úñÇãTù©®*çÞ¯|’™3g$ŽÅårrÍÕkù‹ÿ[Ùû7!Z¿—îßp)ð)ãÀèUI_nÆ’€ñ)ªGŽœà¿p?w¾ëÓ|ä¾Ì#>GoŸ'6Í7ù+MÓ$55…{ÞÿvîûÙ½¬Y½€@ ÄÏ~þGîùà¿°å¥í‰‹0•†I¡ÝFî⹨%–…3Œ)K9 ‘H”G}Ž}â«Ü÷‹?'zŽ·´ñÃý–p8BÝìjþë;Ÿçá¿ü˜o|ýIMµŒ„E‹ÖF„\ƒšõbBm!,­¾±©:‘H”“­íD"Ql6••ÓÉÍÍà豓>rEQ9MÓ‹?77‹·ßv ?ûéWX½zéÏÍ0ÎM¢ý-$#ÚÓoöFpɨÀRÆÊ€£=f½}yìyžzæeæÔÍdÓuëØxÕj*+§' uÄ=ƒ††yüäG_æg÷ý‰ÿùÕƒ øØ±£‘~ø‹üÝÞÁ{ï¾™ììŒ)r%Ò4p©ÖLÎc\ÙxP1bÎ =|ñKߥ££‹wßuSìï‚Í›-ÅäÒÒ"þó;ŸgÉâú˜$V¥´´ˆ+7¬L¸ÒÃîJì{˜ÐB¡½£“æÃ–P¦Í¦2½´˜åËpùºå,]2—ýä~~ߟðùlݺ‡Ë&ade¦3wn ëÖ-cíšfÕVN8ñ-œ$áî>íìEÒˆc2Í@ÒÔurs³ùÇÏÜßÿü$;w6áóïI’ïúŸY¿n7Üp‹Í!##=Q£7 “¼Ül>û™°xѾõíûhܘÞ^ßøæÏعs?ŸýÌ=ÌŸ?û ãú" Õ¦¢Ømôzš¦IGGgÏvS__›ÄHZÒ‹{8Ñh4±#Ï©›Éüy³bç*IKKå_¾ð÷ØíöQ‡KȘâRì 'ôEÐÔt”ÎÎ^2ÒÓøçψ+7¬¤¸¸‡Ã†‚U+ó«_ÿMÓyéå|àž·3§®šüô=\¾nµ³*Ɉ‰“¼µËO “`Wo¤kpÀpД`RÝ€¦nàpع햫¹zãöì9È£>ÏsÏoåÔéd…ߎŽN~ûÀ#<ôðf-œÃ ×_ÁúË—ÇH*Ö©(‚«7®NôüïƒO‰DÙüì«4náã½›Ûn½š”×}ؤa¢Øl‡ý¼×Ü…{7š¦óß?¼Ÿ?ýé JJ ùõ¯¾‘Ôêx ƒ¥RMÓéé±”k5ç¡¿nffu9ŠªbS-ªt~~ö˜”Þ¡!ÀD€aìØÑˆišÔÔÌà¶[¯!##ðò:Š"¨Ÿ[ÃìÙÕØT… W\Bð®;oÄf³%ÔsÞJäM-¤®èî vÃh³.]7 E2$Ha&©îÖ¬^Êe+râÄižÙü =þû›Ž$%•âóW_ÛEeE™•4¼nsçÔ$vyù4¾vï§Xº¤žÿúÞ¯hmmçÔ©3üó¿|‡;÷óÉßMUUÙ…{øÌ¸`?¯+jš’îîvï9HII Ô¡ë:[^ÚÎÉÖvrs³GÞª!»t|á*ŠBJŠÕ¹ØÖÖÁÇ?ùÕØ$YUQIMs³|Ù|>õ‰÷2cÆpi+™TIoý !ð±{ïAæÍ›EjjJÒõ4MIaa?þÁ¿‘——ke½öÖ¢¿(3ÁÛÓçë9 à’”0ÁbÆc¨Âouu93g–óŽÛ¯cë¶½<üȳ¼òê®ÄîÖvìx+Ç~ÜÊxŒËV,䯮`åe‹ÉÏÏÁ’Â;︞ú¹5|ë;÷ñÌæW‰F5þø§ÇÙ¿¿™Ï|ú6^µ»]=¯1ãqH,öžÍfCLÔ44 TUa_ãA>ý™¯søÈ þíKcñ¢¹ ö˜ºÍha‹ˆÝ࡯»\/žËó/l,`¨–~wO_LrËä;ßúÜO a†|÷hPÁ‰“§i9n Ö\¼h6›:B1IUUfΜqáî·0i˜½¯/Y`Ê-ð„c* )M cÔ.ºx7;;“M×]Îï_yà·ÿÉ'>v7µ5¨jò×÷÷ðø/ò‘~™;Þõ)þë{¿âà¡cèºÎÂ…uü÷÷ÿ•ÏîƒäåZÃ@:ÎÇ?ñUîýúèììñ}ç„x@Ucà»cÍ4‡œDÓôDÌ.œ[8êâCÖè×o¿íÚD£ÔXxéå´:“ôž¡?1‘‚Ý» GhX:E çŒiDÿïtܽù „@ðÑãñzÌä6à‹îÈaÿ-M0LÝŒ£¦÷ ìv Ìfþ¼YÜ}÷ͼøâë<òèsìØ¹¿p,˜¦é466ÓØØÌ¯~õ ëÖ-ã†ë¯ ai=ŸøØ{X²h._ÿæOÙ¾½Ÿ?À~ü»÷䳟ù—­X8‰I³c@7Pí*BUÏù²Ji픪ª iCÜc!JòŸtsƒ9€ØßLS2}z1?üï/ñÚÖÝ´œ8M(Æ4Mzû<<ÿüV:»zñzýtw÷Q[S1âz[?-Æõ ବ„Ÿÿô^/šCnnÖ[ný›B õÐ7’0%³†b¨°Æ½ û1=昦Õgîµãñå´’Þu§5,tÏÞƒ<òès<÷üÖ:€gÎvóû?<ÆÃ<Ë‚uÜpýz®Ú°ŠŸÿä^~ú³ßó›ûÿŠ?dÛ¶½|ðC_äC¼ƒwßuYYéç˜H]GµÙçéIÄ[r̸b$0ÍÑ\èQ’€ñÿ.,Ìã–›¯¶Œ/I¾ý_ðÍoß7ä¶ ;Iæ6\qÙß¶NÞÿD{úŒ3ÁÐX³.P)åÙ3]üèǰbÅB.»ly¹YãNç‰ MML¶Ä“†=OÓ£IIÃ`0Ìk¯ífëÖ=üìçdãU«ÙxÕ*fÏ®âÇ?ý=£»§¯ýÇÙ¹k?Ÿùô=ÔÏ­™|Ü*AêªÝª:ñûGA¼Ö?x~ñé9C<€aŸZ¨Kü«$ ø@@zZjâl6gLÚ,=ÍMnnöˆó;—$Ýß¾NÞÿH‹Ð¡0å{Âvà°”º4LÂá?ò¿¾ÿ!æÕ×rý¦õ\uÕ**f”Ž«Ú24i8³ºœšXÒpÛ¶=üõáçxåÕIƒ'¤”´´œâÇ?y€ßÇ’†×\½†¼Ül^}‘h”'Ÿz‰ƒ‡ŽóÉßÍÍo»*AEž¦¡#ì¶$¹ªsA’ø½!!€)‡± cCbŸQT!~g6¿Âïÿð7Þpw¿ûm躪*ôôôóÜs¯PSSÁ´’Âk›ÍFé´"Ö¯_aé ¾µ³ÿmÃ4 tõ†Ïhzœ0¥RàC1V;0ÄxÈšÄ0 CJ Š*Ð4]»°k÷~ñ?ÿËëWpã W°`þlÒÒRÇu5ã‹&';“M×­gÃ+9xè?ñ"O=ý2ÇŽµ&-dÇËOná¹ç·R:­ˆÌ¬tººzhmmçŸ>ÿmvìlâã»›å%ãW ¤Dê†eÞP|.B †É®ÿ-¼^?RJ6n\MnN'[Ûùþß϶×÷!„àúMë5û8TUá£yYY ·–ÿß6̨†¿»/ÐgµWºôÝ€Ò0ä Œ× N:ï~ýüËÓ4,ÇM7n`횊ŠòFÐb“¾|HÒpÑÂ:ÌŸÍ{ï¾…¶lãᇟc×¤a$åxKÛˆï‰D¢üîGhllæë_û Kçë HÝ@Øl (çµk&‡ƒ@<0FQ©5 #Á«OIq!ü¼lÒÓRcnwsϾ@qq>û÷æX¬dwy,1:ܨ)ŠÂ‚uÃŽá-üÍBd8‚·§ÿ¢s`r½†4L)ÇÙÙ}¾Ï=¿•-[¶SSSÁµ×¬åÚkÖR[[1áHïDÒpZwÝy7ݰÝ{¬¤áóÏoãtûY&Âþ¦#ìÚÕÄò†ùcNcÒ]–p>9™¦ÅB$¦&C:õÀòüþ`bØi^~ ™6­ˆ††y<ô×͆ɶ×÷&ýÒŠå ùò—>6fÖþ­…ÿÿ¾ž>7<ü¢Üä‰A0À”º.­Ä5¬§ãà¡cüæ·eíšnºq Kç%f' ݬ]ÓÀÊËÑÒrЧcLæ¦##æ…Ëå—+c!€â°ƒr~]‡Š¢$Rz¦i) *ŠHuÂáѨ–¸.BXC6=>;53g ¥¥êów÷ÜÎþ¦#;fIk©ªÂôÒbn¼qï¹ûf¦•¾µÐÿ@`qz=Þ~9ÈxÓxñÉ@RŽâÞÞxÃ23ÓØüì«ttt%½ÖÕÕËŸÿ÷I{ü,˜Í ׯgÃú˘>½UU& „Ìœ9ƒššÜqû&¶nÛÃ_~–W_ÝEolDuâ" p¹&˜ `š ë()®ón;š1×\UÕDûnoŸ‡ÃGNPQQЦIvìläç¿ø³¥CP]ɼúÚ„7´hÑ~ñ³¯ñü‹ÛBTVNgñ¢9”•• (â­Å)‘ðô¦VÞ°8}º½‹Î€Éô€) CZnòk Ìâï?ôN>Á“O¿Äc¿È¡CÇ’y„Ba¶nÝöm{ùùŒ?qÕU«¸~ÓåÌ[ƒ;%erIÜL®ß´ž+7¬äàÁc<þÄ VÒðx[ì=§sSt›Ã1.yf<(Cˆ7qvž¢òór«ïþk_ÿ1ålgO?ó2§OŸEUUÞõÎ(*ÊKÄôRJfÏ®¢®®1y0“×z+­wQ!„U’#Bëê%z¶3"}ùwÊ”VZ"=ýzw èat€KÛ `€”†)娚nV=»¶¶’ÚÚJî¼ã^~e'}x3[·íMâ·K)i9qŠŸüô÷üásÙŠ…ÜtãV®\œX@cízIIÃEu,X0›÷¼ç^|ñu~ä9öî;„ÓigL,@ÓQöIk¢‘ ÕJ»Æcßõ‚Y³«PUÃ0h:p”¦G“¾çÎw^Ïí·_7âzk—¿„PB(H]Gïé'tô¾Ýð>aš¤L/FkëÀ5sÎÔ”©[†RêêÕÎF¢.’èPLªÐ4 9Ú7UIΈçåesË͹æê546æ‘GŸã™g_¥µµ=ésñòÞæg_¥®®šë¯[ÏÆ«©&$2êñÄ\²Òi…Üõ®›¸ñ† ìÝwˆé¥Eã,(4ML]·ZãÃÆA_Ÿ‡'Ÿ~™þ>úàØl*^¯Ÿh4Š"EEùaϪ˱|Ù|^}mwÒw”–ñ®wÞÀûßwi©î·˜x—1×ÞŒFÑÚ{463°}£'‘Š‚{V97n ½¾Õa§åKßCF¢Œ?FæA¡î¾P·aÄÇ¿) @B…DC¦% ÆÈ’TÒ‡b;µËå`ùò,]ZÏ=ï¿ÍϾÊ#>Occ3á!ì?MÓÙ·¯™}ûšùå¯äŠõ˹ázKH$==-!$2âIô47kbòTãºÎ¦ÄÔt„Ã1áýÔ4/åüá³paw¿ûfìv;Û¶í%ŠŸ—ìÚÊÄpÊüüþãkŸáç¿ø3ÇŽ·’Ÿ—CÃÒzÖ®]FuUÙù÷.¼…7Žø¢Gˆ¶ŸÅ·«‰×÷µ›a0 Ã4ÍQÔs•s¨§KiM›Ñ‡ú¶ÙlTV”Òq¦+‰ýg˜&‡œàð‘üîGX½j 7Ýx%Ë—Í'''sÜF¤Qß0À4'uCEpÕU«øý§oHÉqfu9_ü—ˆõÕå‘\˜ó&@¼ [‚n9…wû>¼;öîìÆž—Cú’zŠ>p;îêrli`˜D»ûð¼¼_óq¢ý^lù9¤××bD¢˜Q8™ÄôQí6‹72u§€îñÒ?àó0R`8.ª(hÂ0Á0 Ã<_`ø—jÃÔh\.'ÿþåO`³©üå¡gxáÅmœ9Óôžžž~úëfžxr óçͲJ‚W®dFù4TUÅ4ñž©`(ö‰o¨a˜,]RÏW¿ò þüç'‘X3ðn¼á ª«ÊÞ*Ó]* ]ô¾¡c­x·ïÅ»³‰HOöÂ|2æQÒ0wU¶´TLM#z¶ïë{ñ7· ù8ŠòH›7‹ÔêrìY(vÑ–6l©ƒ3"½ýà°#ìö)´ê­×#;}þÉp¦O¶x–0 çj€Äü¸8TE!;+ƒ¥Kë¹lÅBŽkåɧ^âñÇ_àPsKÒû#‘(Ûw4²}G#÷ýòÏ\¹á2®ß´žùófYLÀq.´Ô40̘"ðÄP…[oÞÈu׬,?¼Uº»èˆ-z)%Æ€ŸÐ‘x·íÅ»çÑþœÓŠH_½”Ò†y¤Ì(Å–æÆŒD Ÿ>Kÿ Û9á,-"sùË0d¤[ßëÞ”ºá*.Hü¬ôú±§ºÏ“6>yDzúôž`¸ŸÑ…@/9Pš`šºašÃxñžösÅp=:EUPTÃ0P…Ù³ª˜=«Š;ßy=/¿lq ¶½¾¯×Ÿô¹ÖÖvîûÅŸùÓŸžäš«×ðï_þx’ åˆÑu¤aœSLgš§Óûï·þECŒ˜#McÀG°ù8Þ×÷âÝ}ÍëÇYVLÖ•«ÈXROJù4T· #&Üv†Þ¦#·`F£¸ÊKɾ|9îŠéV b Îa‘«4MtŸ%ÕÕ¾ ÑþÔô4PϯqlR&¡®Þè™Hthð›ÂHX¤4 søÌ¡Zxç‚á9EQ,¢Mì”ã ­ ?—Ûn½†k¯Y˾Æfyô96?ûmmIŸ÷úülßÑH(!;{ô{%©éºa¹toá͇!‹^ï xè8Þ­{ðí;„⬜NΦõd,ž‹«¬ÕåÄ 8…ÿa‚ÇZ‘¦IJÅtò®]‡+æ À0%¦h”HW/á¶Rk«°çfYƒfXïµok½\yÙ–§0E#ã¤nèî u›æ%áÀ$<LÓ0MSד—甌c´À¦ª#…¯bå½”'—­XDÃÒy|àý·óÄ“/ò£Ÿ<¤<ìr9±ÛÕ± µ° €©ǹ+¿…)B|ÑzO?ÁCÇغ_c3F$Š«ªŒÜ[®&ca®Ò"‡Ýç'ØÜ‚oÿaB-mU%¥ºœ‚›®ÄUV‚cíI)|3"ÔÚŽïÀ1‚ígÁå$t¼òw¥âÈÏÁ G0¢V P"‘† †°g•3©á‹ç ‰âëîó÷&·_1Ð8&% nš¦ijq‘’ؽã<’€Rލ6uœÏhªªšÎÝwßÌcO¼˜dìv6Ûx§b±¿Li¾å\j YôZwÁ¦Ã lÝ‹ÿÀ MÇ]SAÁד¾ gIÂnCðáß_c3áÖv„û¦‚¢Û7á,-BMq1Ø­*ÒDðj9…ÿÀQBg.îš ßv)EyœºïO–6`F¢Q %–G’h¿5+cJ¯ƒ 1ÐÛïóŒÔ¸h˜Ðè–`˜ºž|dç•hcx“iJtMGÖl·Û|ý19¢”ç5à-¼A áÝk=šŽ0ðÚnü‡Ž#¥Ä=»ŠÂ»o&}Þ,EùUEë÷àÝÕ„¿±Ùªå»]¤Îª"{MÎ’§%…'tIÓDëõ‡Žã?p­§{^6©skÈ»þ œù9ƒß´\{ëƒ&Š”‰¿ Yú 6K³Ñ„05ÝJNÑZ‚¡ãÀ‡ë Ç%S6£`è†aHMGšçF‚x0ÌP•„¢Îd ëúˆïp:(BŒ›¬5cM“-¾…ó@¢ÙFCkïÆ¿÷ [÷<ÖŠpØI­¯¥ä#ï"mn ޼’Hg}/nÿÿÑž>ì9Y¤Í­¡àÆ 8 r6›µ8c?aÕõ»ñ8FàÐ1tŸGAé çZS=7 ÷¥Äðž8‚ÔÙUÖŸu#ªYï`ø‚HUM|Îð0u5=m ‰]‚ho¿ìñªð¦HJ@FÁˆè†QY”Ï•VòÒ–×b–°©6KØr§,DÜ$‡ÓáÿX˜¡0¦”1à-"ÏC|ÑG¢D;:ðï±}èÄ)DŠ‹Ôyµ”~â=¤ÖÍÄž“‰4L"gºè}æe|MGÐûpæ‘6©³ªpäe#TuÈ¢—Èp„P{'þG6·`„#¸J‹ÈZ½wU9¶¬ŒA¡WS¢{¼µáÝwˆÀ±Vü§Ï’RZÈì/T3ª¡G¢ƒ9€pG¼æ/,f¡ÍaGq»¦$ wõigǯ¼)’€¦S×4£¬´þ÷—Ø»ç}øY¶lÙžT¾›ìIß½m65ÑV<1šfŒîŒ÷ŒPw‡·pþÒai?‹?Öa:y%ÍMú‚:ònÙˆ{VöìL¤¦nïÄóÚ.üax8K ÈZ¶wmöìÌÁE/$f0D¸µ_S¬Ä§ë¤Ì˜NîÆÕ¤TNÇ–ž–v“z,8rß¾C„ZÛv©³ª˜v×MNŸ¡÷Ù׋Yjš¥Ö 4¯q¹xA´×ƒ´ÙPbä¯)) uõFº/‘@òd" hê,É8yšô´TKs’­ `³©çäI躞PÙÃ2ã}‡¥ôbaYý·€s†P”ØÛ0‘SƒváSgQ3ÓH_4‡‚;6ᮩĖ•n±ñÚ:èaþCÇ0Ã\Ó‹ÉY· wõ l™éV5 ¾è¥Äðù­ÌýþÄNœEஞAÁMW’R6 5-epЪ¦íì'pèÞÆf"]¨é©VÎàÊ•¸¦YÕ¡(D|±‘©Øìö„`x}(nwâ»Í¶Ô”DÎa* 5WOðìà8ð¡³ÞÂíØr³H[<—»o!µfjFf(Lèd;=Om!pø„µkW”’wÍZÜ•e 6ž5„Îi±ÜõVü‡ ê@q9I­­¤èŽM8K‹­_|C$ÿÅ·ÿ0Ñ>޼lÒëk)ºùjC2ýÒ4I?!D"Þç( Ä©³*qäæ l*Ò4­…oEAQcƒødWâ–ƬgH±~#:àÇ—x¯ÞÛ3JBüõBà éïé°Æ-^TL<¤)MöpϪš«ÛÔs „å ÏL¤{”ByËŽ¡vþ á–¶D[m´§{aé ó(Y6Ÿ”Ê2Tw ºÏOàp ¾½‡¬dŸÝNjÍ Šn»Ž”ò½¦íîÅðþGцdûócÙ~«$'@šV§_k¾Æf‚GO`j:)eÓÈ»â2Ü3gXI¿m8¾Û ڍà œ>é½ÈŸ1iË‚”Ön/H…ÄÄH)1A‹÷HÃú}wYI,$¸ðk2>¼Ï3Ðo&ë¼)<€¤HxÀØl*ûèݰyó+´¶u (qJñäÎ;ÕFx.§s|ÓD…1UËÿ÷†,zÝë'|¼Õê°Û}€h¯Ç´B2W/!céoE·^ƒ£ Ï¢Ó&ºë$º?@èÄiüû› ³†™¦T•‘ã–(Gz*+Ô2#ÑÎn+þo:‚Ö7€#/ÛbûݲgA.Ân·¸fl§EU1Ã<´5äØö]œi:€ìì"'jP-md`Ç[ôC·Í˜ñµ4 »mP70B8-ÀŒDQTÕ3ÁRÓ¬ŠÅT• ƒ@WOøŒnÄu.ªàPL( †Õ  ›ú…5ç‹HD‘íŸÈˆÈˆF$–Rýÿ4ˆïô¦Äð“ÉH3žxìÃsŠ¢ŒoÈH„P8,eª;6ßëÿ( Ð}~BÍ-x_Û…oï!‹*;sùw\Oú‚Ù8ŠòÁ4 ·wÒýÄ‹ø›Ž`øƒoÅ"+)—“™¼ècÂ#'¬Ž¼Ó¨©)¤Î®"{m®iE()±¸Ú´2é¡Ö|û› Ž…å¥ä^q™%Ê'þ Yôñ$^Ôë§ûx+Çvî•-;wã9~œ”Ÿ(0‹°ãÆ…*ŹÈ2®)u7üRb†£8ór¬ó E,é/»h}LA¢Åø‚Cd0Œ§§ÏÛ;Ò\Àj¢ã"¦aJMë¼4D£Ñ$òÑD@`¹yáPÄé!þæ]¡(˜šNäX+¯î·½=ÄU]Nþ;o mÞ,…yHM'|ªƒÎ‡ž!pèf(‚«¬„Üõ+’Êi‰EOÌ5·àÛßL䬣§ÏIÞ5kqå[-´B€‘œ¹µœ‚xæþ¦+I©(Å––œ¹'¾èI¼ylÇnyb×^ü­'Eª?(ŠM•Z츤;ñPÆ“Qç|& šš1R”Í Ån·Œg ˆšâ´*4ÕéŒq¦âæ t_O¯§?˜< àMÈaÿg†)eôB%ßF!©ÊøIDa±µHÔHQPÄßv; ) 8LßS[·uà(-&÷¶kH›[ƒ=?Õµ¶Ó·åuiíÄ:îŠR‘íº˜ fZl‡·bz3ª¡u÷â?t\Z1º{n6éõ5¢ðæ8òs-µ q¬Uúc™{Åå µ¶R¿ó†Q3÷€žhî>Ù~è¨ylÇnyrï>"§ÛEV(¢”J›ÈÁŽS¦Æî2Iÿž˜BÀÔukýH‰‰JÅíú€‘â’B’pgTÝ)»ML… èýôxz° @”ä`(.©(¨;°(1uý’‹Þ›¦É²†ù\wí:ž{~+ápE±)¢Ål“˜‘(þ¦#²ËvnkÇ9­Pd,®'­¾VÄsqwÝŒj„ZÚdÿK;¤¯±[z*é ëDÉÝË…«´8&¤1˜¹Oºg¡0Ýûš=[¶FíÚA¤ã´­ ¬«•Ò®ä`Ž!‹þB>]LUH§Ô ©ºœ1¢¿D`Ku[ùpD:23b¡Œ‰æõKRœˆáŒµ xdZ¯GöûƒXJ@ñ2àE/ü? ®VÁ ‹¦7šeXIfII* 77†Œ”(1 œ2ªi‡¾Ð77GIMP 2.10.282021:11:01 19:10:32  þBHQ6ÿØÿàJFIFÿÛC    $.' ",#(7),01444'9=82<.342ÿÛC  2!!22222222222222222222222222222222222222222222222222ÿÀ"ÿÄ ÿĵ}!1AQa"q2‘¡#B±ÁRÑð$3br‚ %&'()*456789:CDEFGHIJSTUVWXYZcdefghijstuvwxyzƒ„…†‡ˆ‰Š’“”•–—˜™š¢£¤¥¦§¨©ª²³´µ¶·¸¹ºÂÃÄÅÆÇÈÉÊÒÓÔÕÖרÙÚáâãäåæçèéêñòóôõö÷øùúÿÄ ÿĵw!1AQaq"2B‘¡±Á #3RðbrÑ $4á%ñ&'()*56789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz‚ƒ„…†‡ˆ‰Š’“”•–—˜™š¢£¤¥¦§¨©ª²³´µ¶·¸¹ºÂÃÄÅÆÇÈÉÊÒÓÔÕÖרÙÚâãäåæçèéêòóôõö÷øùúÿÚ ?ùþŠ( Š( Š( Š( Š( Š)ÑÆóH±Æ…ÝŽTd“@%}ÚsÆñr221^ßð÷àçjž#N¸híþÍþé>"ðâK!ÍšFê»c–!µ–±u•ô6öqZIê|‹Ez'‹þk~/sf¦þÄs¾1ó(÷猥X«ê i)lD ã¾ÂQEDQ@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Š'žTŠ%,îvªŽ¤×Ñ¿ ¾[ø~Þ-_VfÔÝC"0È„yïÁ?G«ø¢Kû„Іÿ|ô¯¥+š¬îùM×¹7aEVDŒ‘\‹þè^(g¸ÛŸ ëµå_¤Vðmʼ·G?¯U®)|LÞ¦ëÑ~AER (¢Š(¢¸ø·R ú/…­d»Ô˜m’T$?SÓ4 nnȇâWŽ<9£iòi·ÖñjW’/Ø'»ßξdoÞÌÆ8ñ¹‰ ½½«ÚtŸúŽ¥ro|G©myçDù˜þ5éšï h ¦ÛNŽIGü´”n5¤d¡¶¥ÍFÜ­è¾oüšôox›^aö*b‡þZH6/ækÓ4€,vË®ê@zÃl3ÿ÷UE ªG@¦Ó8RXú/4I2ŠøW߯üžÑ<ᯪ›-20ËYöüÏJé 0£ÌÏÐ*~MBŸ¾LŸï?.•”œ¾&PÔ4Í'UŒÅwa àô1†ÇãÛó®Wø%áÝMšKU“MrM¼gÔƒü«Ó€ÀÀéHH$€=M µ°ÕI%oø'ΚÏÀ~Ïsé—6÷ÑŽŠNÇüÁêž×ôg+¥\ÅŽå ˜¯°ÌÀð€¹öéùÓd…®¤ªžYê¥wgóâ´UdƒÜ{«zAÁzJúãTøsá}]\i‘«·Y#ù[ô®Yøm&çÒ5ŒõÊ2?:ÑV]Eìâþ÷ž Euž%øw¯x\½Š6‡´ˆàçðë\™85ª’–ÄJœ¡º (¢™EPEPEäåpˆ¥˜ôf€ÜmeôûÄ 5´£=2µ¿iàÉ¥¶ÜN"$ghÅDªF;³haêMÙ#°ø'âÛmS¸ÒïçX`ºÃFîp¡‡½}k©Ø_3-¥í¼ì½DR«ùùÃGÐí´“¸|ò²òíý+b+™!¹ŠKy9îYàŒzƸçQ9]ëåÌõ>¢¼:ÛÄ:¥Èž+û–pyÝ#8?PO5ìz>¥«¥Áw).€°;[¸4“¹Í_*VoRõQTs˜¾ ¾š?²é¶m¶îùÊ+qË7à?_Óôëm6Ù`¶Œ([»äžæ±’ëÇómØVÊÅTgøYØ’ +ÉÝþ±Ù½‡òÿHÚ§»›þ½iãV+Ì?…FHüºRn™ú(Aþ×'òãR*ª(UP v´ÌH¼€Ö3?×§åR`µ1¦E8ÎO¢òi7LÿuBVäþTRZˆÎ™!3!“Ÿ× £ÈVÿXÆOféùt©@` @´"ýóÿv1ùŸð¥&rÙsêÇ4²K(^GTQÔ±ÅaÜxºÀHa±IõúlµMÃ>í÷GçH¸ÂRøQ¾Esuog Ms ÒÌÂãR3jw÷Ž\ì½û8Gã—ݯü9µ™°ÒÅ—`¿ž*FddûÀcß ¦ßŸw rOCMË•`×ÔŠHöù*Ìs‘À¯iº¾¯£\‰ôë¡.7+ý}*‹ª‰8ÏO@iY™ñ0O$õ§q8©+3Õ<;ñ ÇVQ ôfÊäÜÆÇÙ«±V ¡”‚ ŽõóÛ\ù$ãVæ‹â}[D!¹2Áœù3 Ê>ÇçV§Üà«OZg ±x¥ðµº§—];ʈp[æôŸÊ¼óÁž!²×|k®˜L‘4ÑÂí8Ã(ÚFG^™¯DDDE =…R9q*ÓWì¿$3|¯÷Shõð£ÉÝþ±Ù½º IÀ÷¬›ßiVRMÇ?O&ÝLùŸ3ÆRvŠ5•UF=¨fTRÌÁTu$à ç ÿ‰5>,4ètøOü¶½mÏd_êi#ðŠÜ°“[Ôï57ëå»yq¢.?ZW4öI|rûµåø–n¼Y¥Ã1‚ÞG½¸òÊÕ „}qÀ¨Dþ%ÔÿÔÛA¥ÂŠcæIÿ|Žç[v¶V¶0ˆm-â‚1Ñc@£ô©è<#ðÇïþ­ù˜øRÖVjwŒž“·Éÿ|Ž+j xm£ IW-*’–ì(¢Šd…NÿU°Ó"2ÞÞCò8Åê¿´Ë}ɦZËzý¿wæy?•&ÒÜÚ–­WhFä¿ïEŸÃ›ñ¿kNV!é_,W¥|Bñ~¯â-1c¾–·‚B˜óÉ'“Åy­tPiÆè1TeAªsß¼šÎ5šöß§ñ5ì—¿¤ðÖÛÕXç²uR¬©Ê?‹ükÇ,N5 cÿMWùŠûNÝM>u ¦%x©¯«±XzΔn—SÀ0e8\jqr~W@©!]fŸðÊ%‰´µ™ó’° P=²rO׊j-˜ÏN³´y¬n’æÀ2]§Üh×,O§½z‡õojv£íšMžGÊç?]ƒùfº­?ÃúV–CZYÆŽ7‘–ÇÔóZUiXóëb£7¤~óžžð†Ö5K‹®æÏ•ä9#êMkÙi–Ztb;;X¡QýÅ­QNÇ4ªÎJÍèQE2Š‚êòÚÊ5ÕÄPƽZG ç\~«ñCD²,–K6¡(ã1 ©Ÿ÷ôÍ&ÒÜÖ• •]¡½V»Ô-,"2]ÜÅ rìxö§ñ+]¿Ü¶æ+Ïh†æÿ¾rw77’™n§’g<¬ÝUÐõhd•§­Gc×µOŠ=žä²I/$Ða3\N©ñ^ÔrÈ–QÑ ·æk–‚ neAI!è¨2Mo[ø3S”)¸{k=Ü(žL1ükž®&0ø¥cÓ†_ƒÃk=_ŸùüÒIs)–âY&”õy±üÍ6­êZmΓzö—J‹ÎTäê*¥ JK™¤9yS†Ç?â—Ž¼~®Oä?úõÌV÷ŠdÍÜÿu üÏÿZ°kÔëSGÅæ³æÅÏúèMgÅìõÑ}­gÿ6ÿõÍ•|Qkÿpÿ×Eþuõn½ã‹!5%akwS*Œ˜Î:‘éJª»V9ðþgaQÏw0I«º9«Pi–w¶Ú…¤wvsÇ=¼£rIeX}jzÄ“Îï>Kç1³½Aè²)Èü«ˆÔt}OG¾hµ;B‘Ÿ¹2Ñ·§=¿÷ºŠ{xn¡h§‰$† °È5.(ì§œ_½ª>÷›£QŽ‡Þ†c&èñƒŽy¯FÖ¾&]’"r~Ï!ùsì{WŸÞØÜiÒ/`h&^JIÁ?Cßê+6š=U¡Q{¬„°DêHÆ8ïMùÊ…faÜ~•{NÒoµfÎi4Ý·;?<~µÖé_ .ߪ^ÇÿÏq¸ÿßGü(I±Î´)üLàòª¸vØç8>µ¥o¢jwp+ÃaqŒü§aþ5ëzg„ôm+ of(ÿ–’|ÍùšÚ€0=ªÔ;œsǯ°1Ó~ß]G—òÇn§–˜å]Ηá­#HQöK(–NòËëšÖ¢©$Ž:˜Š•7aES1 *9§ŠÞ#,ò¤q¯VvÄ×)©üHðý†å‚v½”v·_ûë¥&ÒÜÒ• ®uõ÷0ZÆdždwlW‘jŸ5{¼¥Œ1YÆ{ýçÿëW{¨Þê2½»šv?ßlË¥C¨–Ç©C%¯=gî£Øu_‰:Ÿ¹ ‘ï%¡ŸJâµO‰ÚÕîä³H¬c=ÀÞÿ™ãô®*®ištš¥üv±²¡nK7EµŒëY6ôG­K*ÃP\ÓÖÝÈnîî¯æóo.e¸“ûÒ¹l}=)°ÛÍs ŽžGôEÍ^žÍ4]oȽíÄÙehqŠéìõã.ªÍgk ·TyKÈ$õÍrU¯$” ®µõ:§WÙÅ:QÑÛÓSŸƒÃWí¨YÚ\¨·k’v–ä€z~y—ÃZKa6£:œ÷Pg…®'½ñM¼—¼ŒÎÙÇ$Ö~°žG¼½’úbĘ­ÆÕô-þŒç'>I·¶Ñùÿ[™TœÜù*7¶Ñ$ðýêßx¥®#·ŽÙVÝö¤}s7sI=Ô’I#;<±Éë]†d†_ÞM>L?f¬yÎÑYVuªÎ6!Kps-Ãpˆ½Îjàá ’“Ñ$¿Ré¸S©)=Kõ4üXþjé29ÌÍd…Éê}?­su©â B=GUwƒþ=âQ ?î/²ël<\i$ÍðÑq¤“8ßI¿WqýÅUý3ýk*®j¯æj·-þÙ—N½ÚJÐHølTùëÎ]ÛüÉm¿ãî÷×ù××ú—†ô¿ø~ =RÕfCíl|ÈqÔÕò·ü}Cþúÿ:ûSOÿu·ýr_åYVÝ¿‡óïºX9;G©¸ükÔ|'ãm#Åö"{ €˜Þ[¹Ã¡úWA*«ÂêêJA¾bðï†õcÅþ ¸ðíà´½ÓîãQÀp]¾\þ•¦µ3Øú‚ŠóüSÞÿax²¦êÑ›äQÏô¯N2†R<‚;Öm5¸Å®3âª^G¡@ÊmN<äg 8ý+³®cÅc~§áÈýu(Þ¥›áݪ'ëù*"ÆB¨à0:Š)˜…×uK;QÔ±ÀÍêž=Ð4½ÈׂâQÿ,à¿^”›±p§9»A\é©’KHZGTQÝŽ+ÉõOŠ·óå4ÛD·SÑå;›òé\f¡¬jZ«¾½šp„¶òT:‰¥ š½Mgî£Ù5_ˆZ™¹äÝL?å¸ÝÏ× ®'Uø§ªÝnM:Þ+4=þwÿú×«È 9=§:'ˆÝéºn¬ƒ$B98î*®™ÇƒõÃêðüz¯G®.±áíFÛRž4™’0>€žµ‰o©¤ æå’÷#oÏ)ÍiN3ö~Í­¤¾ë¦iN3ö~Í­T—Ý{—ü¥µðÉò_•Lþ‚ÚG›VÔ ¶Vb|µ;œþÎC<¶î^6#”àâ§·ÓïõÏom<ÿÞuRZÖ¥9)¹óY;T¥%7>~TìhÚêöÚ&«<ºl^| ›ÏïÇ$Н¨ø‡RÕÊž}°v†1µà:þ5›$oˆÈêpU†¦Öª;óÚ﹬hS¿=®û…!8úRÔ7OåÚLþˆOé[#IË–-ö8)ß̸‘ÿ¼ÄþµäæŠö²?:nîä¶ßñõûãù×ÚV‹¦Úî`3õ>Õñm¿ü}Eþøþuô޼®x²ËG“F¼ŽØÁ´¬„äJƪ¼•ÍWðþg¥<‰å6zõâÿÎïxµ½eÏþDzÇÿ…Oñ!„ÖãÇû7òéPYü&ø…¥M$Ö„0I'ßhnÝKýxç¯zJ1I«™žÉãhþ3³Ù}ŠéînâáÐÿQìkÏ,µ|+º[-r95_–Â\§-ÿ=Sø¿ÜÖÛpšÑ.“ñÊ1Í:]Äà ùD0÷È%ѵ`=›FÖôíOŽûLºŽâ2žžÄv5™â­¯øq-Ë>O²ñ¯7øUáh2¸¹Õ¬$´±š,U1™2¸ùCzÞø¿#,::«2–™ó´ãµD£±Õ„ƒ©UC¾‡Y«xÛAÑ÷,÷«$ÃþYCó·éÓñ®Uø±}6äÒ¬c·NÒÜíÿ|ŽækÏ 8V¥‡õ]Dog!Oï0Ú?Zå©]E^NÈú*yNŠæªïêG¨kz®¬Ûµ ByÿÙ-…ðTrB¨ç°¯É¥ÜYjÙÞÂѳ:‚=A8à×E©êv¾¼’ËNÓ` ž_˜šçwt ¹›;ùáNУß¶Ç.4û‘-¼rBÑ›† qŒòõ£P²“N¿šÒR¥âm¤¯J¾º•ö§­XK{#>&M™n+{[Ò´”Ö®îõ-KäÜ „e¿É×”&”ú®šõÄJJ}WMu¹•,qÙø_M¿‚5K¯´¶e“Ó>•­{§ÇâË{MN×jLXEvðúµUñÙ¿…4ãcÇoç¶Ðç'¡æ ðœŽ°jê…6ŒHÏzç|Ο¶ZI7÷7±ÌîéºÑÒI¿ÅìQÔ¯–û_‹Ém¡d†ôE8Ÿ_ƦñüŒsEQúV5³º…˜áC©'Û5×jº¯‡´d½É}pØÀ< Àýky'J¤ybÝ“_‘¼Ó¥R<±nɯÈÌ—u‡‚£…ø’úçÍ zìQŒþtx¸·XzXÅýk/SÔçÕnÌóà`mD^AU^GåݘŒ±Ïtè´Ô徯ï4§FJJrßVþfŶ¼÷MÌtcºÝ(ÿŸæj†¥¨Íª^™•Tí ¨ƒ…P0©ôM$ë7Ín&XU#23°Èc?η6øSHêdÔfŸv¦R§Jo–-ËËÏð[)R¥QòŹyyþ/miqy/•m ÊÿÝQšèm|¨JÝM®î‹#rSøvìIy­][§‘›fdUþòŽÿXÕ"¥ž]à–bNÑŸ^Ô§R¬œ’j)|Å:µdä“QJÞ} ø~Tñbÿ|:ö‹÷×—ÍáÏÛ1Û4hvgœà.:Ƥy¤‘ªþÌïeøÓà¨æ1‹ë‰8Þ–ÏÔVÎñÂzã¬vZÕ¿šÇ)³“ìü+Ð~ xjÃNuZöì¨ó$fÀϰíPjÿ<7xŒl$¸²”òmËùÎÐ'SÔ 2#ÔR׃›ˆ? dÇ+êú"Ÿ™9p«ô꿇¢½KÁþ7Ò¼e`&²“dê?{nçæCýEKµAs¦¯-øÂ{¡¯ûSÐz•y_ÆøúÑ>“ìµ”þwåªø¨o†•[Äv!€ ËÐý*}s[Ô¦Ô®ak¹qÊʨ§}ª ÈÉaÿ]EG¨Á-Æ¿w(Ï#NÀ*Œ÷¯2Q‹¯yt_©õrŒ^"ò[/Ôè/ækí@¼˜îœN#.z Mâ [NÓµ©öiIq{Áin*8ÂÕZT±:>”\n¶ey`ÄÕ½^ãÃGS–òie½•ñˆ¢áGã\qм[M­mo](Å^-¦Ö¶·©†5K[_±šä®VdU 7 OÈϨ×_è)º–²—oÙ,¡³Hry|¶{jZÙ I=ć'$×u8Y©µÊ’µ¾gu8ZJmr¤­o™¡y©[Íá» ,f†Fg〽gÚß\Y¬Ë›ɱñÜR]YÜÙIåÜÂñ9äÍ[Ö4±¥Én‚BþlBC‘Ó5QT×»½îÿR¢©¤¡¿5ßêgO@Oj–âÖ{Iw4nT0Vàô­hÑáöÿÚxÝŽqåŽ)þ0ÿÊ׼ʒ¬ÝNKwü,®ÝNKwü,3Ã6¶SÍ{=ôx­mšald‚+^ÃÆ=äVÙV°ØÊÂ2Š2FxëÿÖ¬¯ ÿÇ®·ÿ`ù?˜¬‹ ZúÝGS*ùÖ3¥³Ÿ?M¾ãÒYÏŸ¦ßqÖéºwØüW«YÀ¹ÎQŽùÆQ‡Á—Gçj·¶Ö¶à·åÓõ­12Õ6!ãÓ䈸¥KÝN|=̧©åçYRö’ÕJÚ+™Qu$ÛR¶Šïät~0ÛÝê䨸‚8Ž›ÔëZZ7‰-õY_Iû,z|s)X^Üàƒèk#ÃÑ=‹k]*Uµ «s\ÜR¼$±œ:ÀûÕKÓ÷ÒÏä7‡iϾ–#{MÑ¡_>—ªgø•NHÜÝJ¾Õ~ÚÉ*G ²±&vm ëëÒ¨k:Óë—¶ò¥±Žá.c$³Ÿ\ Ù²ðÏ‹õø‘'iâ¶Ç[™»ßñ­£O+I;]k׿…R¬é¥9ÍFëTÿ4dx“P‚òú8m[u­¬b(ÛûØêÈŽ7šAHò9訤“ø õ/á^Ÿ×Ôndº~è¿*ÿvz~§iQ„±³†ê‹Éüz×e,?$T{SÎiRŠ…{wâˆÊÌ ß x†ÛŵÕmË*üëÝpGç\'Åÿøûѿݛÿe¬ß‚mmªø“BS›x'ó"Ÿ3)ýkOâ÷ü}èÿîËÿ²ÔÖVM†U®.×CƒÒ¯OÕ-®ÝK,N¨ï[7^/“2ÿgYÃgæZErO|×5Eyó¡Nræ’¹õó¡N¤¹¤®:IYI³±É$òkSJðåþ¯OŽ;u8i¥pªó¬šémìnµ[Ãi ÊÿnbU}6 šópŠåvÔšópŠåv»·¡SXðñÒí"»Šö ÈŠ‡­éÔÕé/Ã:dÖ€%ý g—d¢J~¡¦ÝiZÌg÷×äaʯ Ï­Qñvá ”v }1\ð—¶q„Ö¿;lsS“¬ã »­~vØ¿5ìºÿ„nd»"K«)–CÔ©ëš«âßøø±ÿ¯e¦é¬`ðŽ«+$t}Îy«ž!Ó®õôË›Hd’ÝTÁ©‡-:ÖÙ&ÿ$L9iÖKh¦ÿ$QþD¸¿ì)ÿ´Å/Œ?ä2Ÿõïò©5xÆ“¤iúL…~Ñæ›™À?pžúâ´õ cÃFt¹{i/®Dj»O0?Ï­ MMTŒ[O›oã&¦ªF-§Í·ÈÊð´RO±HÏ#Ø:ª¨É$‘Å[Ó|>ú+.«­‚8¾há, »vàVLž ¹‹QžòÁRÄÌ‚=‘€¼tãÚ¦´Ð|Aâ¼Ä·¸˜Ÿùk9 ~f¶ö5g'Ñ;_¾ßpêó')T’Œek÷Øv—¯Gk®]êp´Ëq¡@zî#ÈU›\G—§Û[ÙD8Wšét¿… v¾©}XàÔ×k¥xODÑðÖ–y£þZÈ7¿æz~Ñõr—4‘çb3 "•ãgø=eáŸxŠv¸K9±)ËOqûµ?Ÿ'ð»-+á< µõkæ˜÷б>§ô¯I¤fTRÌ@P2Ií]q¥ž}l×QZ/•yºo‡ô­!6ØØÃ«Ë©ëZuÉꟼ;¦ÌÖñÝ5õØãìöHe|ûã§ãYš¯‰/¹7ž0Õ§';®œìòª¢½â¾Ã1(¢Šé2úøÿÞξ¡¾ø…¦xF-ÏTŽQÕ¨u™Bã‘ø×ËÑ®O÷…}e¤éÚ×…lmõ+(.¡hå•víéXUµÕÍ—ðþe;¨|%ñKmo|ÊU†øÏ¨î+¦·-m¢·ˆb8Ô"`+Ëu‚Vi;^xcT¹ÒîAÊÆ\²ì~ðýk5;XÿgÏo¥é÷ke)ºo”!éÈ®»Ã¿ü/â°›£cvxò.þCŸ@zÏ5¬žð”—Fõt->IY·™ A²ÙÎ}(V‹÷z}ð'B¾†O_½FU½*±.%›èIý+Gâïü}éîKüÒ½Ec@ˆ¡T ¯.ø¹ÿZOû’ÿ5¬«K™6z9Jÿk‡Ïò<ÞŠ(®SíºX5 ½?ÁQµ¬Í’õ•Š÷EsUÐé~ ³°Ñ…¤új^J³SÍ?"äœw".QVõØæÄÅÊ*ÊúìJ·'Á7ow$æ\'’dbIÁÉÆjåÍ®›âX`¿:”Vw 'I\wÏjÚÝæ±"µË"Æœ$Q¨¿APYi·Ú”‚;+I§cýÅ$~uÃIû×å•ï§ŸCG–<ò—+½þþ††·jÐA¦iÄ›;rX¹ë+÷jŽÏÄš¦Ÿgö[{’±vÈÎߥtš_ÂÝVïkßÏœgªÿÀWm¥|;ðþ™µÞØÞÌ?Žèî÷ÏÝý+ª8HòòÉ]yœus%8ò/üÏ ²Òõm~ážÒÖâíÜüÒãåÏ»+³Ò¾Ý˵õKÔ{ÇÌß™ãô¯UDHÐ"*ªŽQ€)ÕÓQG™[8¯=!Ҽ¡ihl’IGü´—çoÖ·ÕB€aX%ñ–“ác wÆw¹¸ÃoM#ÉŽ¸ÇPðÇí¼G«Ï¥É§]é×qÆ%Hî€ÓÔbµQv¹åΤ¦ï'vuùÅfK¯é‘k±h­r¿ÚÇæ,#®ßSùW/ñY¬¯|-¨E+"C«D’(<2¹ søf¹KíÃâ9ñoœZõOìÞ>êÆ‘6ïü|~´ÔnEÏC‹Å+$Þ &%[}#ä/ž]ön#ò"¹_†ºî¥âíÍ'ÄR<““ª7ÊD2  {pWó¬­RîX~]bšç]Ôä\~H÷ö÷Ø¢—MÔõ[Šºf¥©hÇHµÕ-M”q4›™¶r p0pGÕ\º1º‰¦é?/­të(mà‹L„„Dæ.ù?^gÞhÚ‡ˆ>,ë‘Yë3é°Ágn³xôäWO¦ÙÜÿÂËÖo^ígQÊGƒ9 ~có¬ÝWá¼ÚÏŠoõYµÛËK[µD{{6ØdU›ñ=»Ñ}FTñV“c¤ø=.ÎæK¤}JÙey¦ó]˜Ì™ Oò«Zÿ„o­üS¡øƒÃvñ$ð°Šî=Áá#œÿž¸®§EðΕ éÉcen|•3÷ÌdbÞ¹lóZõ<Áb¶¡(ƒN¹”ð&?¥|Yu)žòy˜åžFb}rs_^xâëì~Ö' ´‹WúWǵ­¥KàAEVæC£ÿX¿Q_Ax»^ñw‡¼;¢ê: ° \f=áO$uÅ|øC_bø]áŸÂšdNQ‹Z!d89zV5]šf±þõÿ3Ìt|Iº°Käðõ®£jà2InÀqî7ŸÀUµø¿¬Û«®¯à­B1XØŒÀ€·ªx#Rðýôš×çHÇtú\‡÷zíþéý>•¯áXx†F°¼´ýf/õ¶W îW?xT6·HƒÉõ|5ñPfÔ4{½>çþ{D€6à=À‹Å÷ž‘_Ã>&–úËv>ÍwGÐÿLWÒ÷:F›z¥n¬-§Vê$‰XÌVßÃ_Þ!Y޾k†¥¢w~G“[[Oy7“k“ÉýØ”±ý:W]¥|4ÖﶽߗeþùÜÿãõ¯_³ÓìôøD6v±Aè± QVkEIu…X`þµ†þÔ|Aðj+{eUÕ/d¹Îѽ¤ÜN~•êRD“Fc‘ÐõV” 0aR¥d9]/Âe“Aye^“lcŽ 3û»Kg鑸×K5µÌ‘I4 #ÄseÎÓê*j*[¸Q@Š(  ã×Ù¾_€Ø2²Gù°¯–«è·^_„ìm³ƒ-Ð8õšùÞº(­{$QElfôtžºñ7†<=ªiºÄúv£mb‹FH qÏa_8×¼Oñ*ëÁ:_†àû"]ZO`¬ëœ0 ‡ñ¬ª^êÆ±þõ_©e<]ãÿ°‹ÄºIÕlW¶[›§~`}kJ]WÁà˽zª|ÑKŸ*hÛØ÷­ŸüQð¯‰Áöĵ¸~<‹¼.O 'ƒGˆ¾xgÄ[§û/ØîÛæ6§aÏ©ìk+¤õV ͳñn±àë¨ôï'Ÿbä-¾¯ü§ÐHCï^‡oq Ü =¼©,N2®‡ Šñ›ÿ ü@ð¥´Y͉tf=¬Êöóü'¯àIö®;Hø{àý\Çgeq‘?¿ÓnXâ#þÆy_¡§ÉͰ\úr±ç@Þ,´bÛk!ß2ÒøgÄ6ž(Ðmõk%u†l®0A~`ÓØgÅ íhßú¬š5§×Ñš•FóX±±Ô,ìn& sxH‚>íŒgòÈüêõp>.ãâg‚¯Úö4®ÌΓFñ k®­d4ÙÓY‰ûä¨lnEqÇZÖ®tg–k›9Þ+@‹óF¾X8P^I­/qâßú~Oý•‘£j·6ŸãÝ^ÊÓí¯¤Ì±÷€DÝùŸÂ© 9íÄÿ.íô2ãO’Þê)ïhÈ>pî[ë]Æ áz5Ô ~f°n¼Cañ"ÖÇHÑ4¹LÏ4S^\´;RÜ+a»Ôãë]ÿŠ|0ž'Ñ!Òä¹h#I£‘W$…íMèÓb7ëÌãðŽ<[¬jšªÜ5­¼«kjåRUQ’Hî2Hü+Ó)B@*°ÎNøm„Þ$²†t‡EÕ‘Q-ãSº/”V­Ÿx @ðɬm7\ãyNçüÍtÔPäØQHaEPEPEPEP„~Ðw@ÞhÖ™åRIú?¥x¥zoÇK±qãÕ€ýžÕûIþ¢¼Êº©/t*oo ¢Š+C0¯PÖH—Zøz¤1n?õÑ+ËëÒ¯Ÿ~½ðÿØÛüˆ•œ¾%ó5ðߪýOeñÂÿ xˆ4’X­­ËËka°çÜ yƹoâÿ„PÃsc®}»J’O-mîA`8ôíÓ±ïC¥yíØð®˜¾·¿û#V0m»2YcÃÿt»¿* ~Îm*y‘h˜„dÃõÚ^éñy೿‰‡Ë*à°Ì9ªzw…´}gÁZE¦§§Áp«g‚Ê20€p{Wšø×ÁS|8°mÃ:ÕÝ´k"«Z³n^Hç9¢ÑoMöè~Ñ¢Ò¬7ýš&v@ç$nbÝUñCŸK1ú¿ÿZªx'S»Ö<¦jÌæxC»€I«Qs⻯E³Œ~nõœ·ÔÒžÏГ^Ô¤Ñô+ÝF+cs%¼EÄ@à¶=ëÏôírˆ>3ðæ£¦G/‘¥Á+ÝÈÑ•T‘Â|€ž¤=+ÔC)VƒÁ½G´ɲ’5ôEšv38-WÀ̾*½Õ4o˦Ûê~Õ.X1•ÏNu¾Ð,ü9£Ç§Y†(¤³»œ´Žy,ǹ5«EMè#Š8WlQ¢/\*€)ôQHaEPEPEVãQ´µm’΢CÒ1ó1ú(æ€I½jŠË—T›h1ÛˆPð$ºp™ú/SúT;.îþü“ÈdJ~¿5+–©¾¦”÷ÖÖßëfE?ÝÎIü*«êr¸Í½£mí$çË_ןҠŽ{w*$dî–é¹ÿÉ¥LA‰|ÑgÆ@-3eºã§?΂¹b¼È%šîKK‰¾ÞD¹ xí’ÙÏé[)’ŠO\VMñͽÞ?ŽXãýGøÖ¸P)ì¿®ÁE“LÌù7âà½ø¬¸9 (‹?î¨Ò¸úÒñ ßÛüI©ÝŽ“]HãèXÖmvAZ(U>6QEQ_Kx?ÁÚ/ˆ|#áÝNîûm–É!š7 ‚¬t#Šù¦¾¬øJåþédõ‡þsù‘N†-ór*sÌVQrÞnOãÅN!’sŸ&I?Ú¸lûäUKí[MÒЭþ« ËH_Ú »~êü <[eÒ+{RF<Û†ó$?^rïª6=ÇT¹ºÿ®§ÊŒ~PkŒ¾ø—¥Ù’4½=¦“´²ü¿¯Zåu/ëúŽT] hÏðÂ1ÇÖ¡Î(í£–âjëËo_êç­Í,VëQ·²F`«;W$ðO_À DO*ˆƒHÊ/cU.åÎÎä“Ü×…XË$úÝ”“Hò9¹,ìXŸœw5ï_x‘ýëÑúcü)Æ\Äã0 %ï}Hçù”/üô¾ü9þ•±XÀî—OÚyòÈ­š£‚§@ª:ÍбÐïîÏHmä“òRjõrß.ÍŸÃÍnPpÆØ ÿqýi“‰\ùØ»³ž¬ri(¢»Œ[¾¡EPÿÙvÞr%tEXtdate:create2021-11-01T18:11:47+00:00¯ôž,%tEXtdate:modify2021-11-01T18:11:47+00:00Þ©&tEXtexif:BitsPerSample8, 8, 8í>'tEXtexif:ColorSpace1›I!tEXtexif:DateTime2021:11:01 19:10:32=I>VtEXtexif:ExifOffset190LŽóÂtEXtexif:ImageLength1079²QœätEXtexif:ImageWidth1079!‹itEXtexif:SoftwareGIMP 2.10.28Ëð$tEXtexif:thumbnail:BitsPerSample8, 8, 8 ôStEXtexif:thumbnail:Compression6ùepWtEXtexif:thumbnail:ImageLength256Pp0tEXtexif:thumbnail:ImageWidth256ˆú(tEXtexif:thumbnail:JPEGInterchangeFormat328—ÇáÁ0tEXtexif:thumbnail:JPEGInterchangeFormatLength13905þ@’F*tEXtexif:thumbnail:PhotometricInterpretation6Š tEXtexif:thumbnail:SamplesPerPixel3á×ÍZtEXticc:copyrightPublic Domain¶‘1["tEXticc:descriptionGIMP built-in sRGBLgAtEXticc:manufacturerGIMPLžÊtEXticc:modelsRGB[`IC tEXtunknown1Ú!U|IEND®B`‚dehydrated-0.7.2/docs/wellknown.md0000644000175000017500000000516515012216410016770 0ustar lukas2511lukas2511# WELLKNOWN With `http-01`-type verification (default in this script, there is also support for [dns based verification](dns-verification.md)) Let's Encrypt (or the ACME-protocol in general) is checking if you are in control of a domain by accessing a verification file on an URL similar to `http://example.org/.well-known/acme-challenge/m4g1C-t0k3n`. It will do that for any (sub-)domain you want to sign a certificate for. At the moment you'll need to have that location available over normal HTTP on port 80 (redirect to HTTPS will work, but starting point is always HTTP!). dehydrated has a config variable called `WELLKNOWN`, which corresponds to the directory which should be served under `/.well-known/acme-challenge` on your domain. So in the above example the token would have been saved as `$WELLKNOWN/m4g1C-t0k3n`. If you only have one docroot on your server you could easily do something like `WELLKNOWN=/var/www/.well-known/acme-challenge`, for anything else look at the example below. ## Example Usage If you have more than one docroot (or you are using your server as a reverse proxy / load balancer) the simple configuration mentioned above wouldn't work, but with just a few lines of webserver configuration this can be solved. An example would be to create a directory `/var/www/dehydrated` and set `WELLKNOWN=/var/www/dehydrated` in the scripts config. You'll need to configure aliases on your Webserver: ### Nginx example config With Nginx you'll need to add this to any of your `server`/VHost config blocks: ```nginx server { [...] location ^~ /.well-known/acme-challenge { alias /var/www/dehydrated; } [...] } ``` ### Apache example config With Apache just add this to your config and it should work in any VHost: ```apache Alias /.well-known/acme-challenge /var/www/dehydrated Options None AllowOverride None # Apache 2.x Order allow,deny Allow from all # Apache 2.4 Require all granted ``` ### Lighttpd example config With Lighttpd just add this to your config and it should work in any VHost: ```lighttpd server.modules += ("alias") alias.url += ( "/.well-known/acme-challenge/" => "/var/www/dehydrated/", ) ``` ### Hiawatha example config With Hiawatha just add an alias to your config file for each VirtualHost and it should work: ```hiawatha VirtualHost { Hostname = example.tld subdomain.mywebsite.tld Alias = /.well-known/acme-challenge:/var/www/dehydrated } ``` dehydrated-0.7.2/docs/tls-alpn.md0000644000175000017500000001043015012216410016471 0ustar lukas2511lukas2511# TLS-ALPN-01 With `tls-alpn-01`-type verification Let's Encrypt (or the ACME-protocol in general) is checking if you are in control of a domain by accessing your webserver using a custom ALPN and expecting a specially crafted TLS certificate containing a verification token. It will do that for any (sub-)domain you want to sign a certificate for. Dehydrated generates the required verification certificates, but the delivery is out of its scope. ### Example lighttpd config lighttpd can be configured to recognize ALPN `acme-tls/1` and to respond to such requests using the specially crafted TLS certificates generated by dehydrated. Configure lighttpd and dehydrated to use the same path for these certificates. (Be sure to allow read access to the user account under which the lighttpd server is running.) `mkdir -p /etc/dehydrated/alpn-certs` lighttpd.conf: ``` ssl.acme-tls-1 = "/etc/dehydrated/alpn-certs" ``` When renewing certificates, specify `-t tls-alpn-01` and `--alpn /etc/dehydrated/alpn-certs` to dehydrated, e.g. ``` dehydrated -t tls-alpn-01 --alpn /etc/dehydrated/alpn-certs -c --out /etc/lighttpd/certs -d www.example.com # gracefully reload lighttpd to use the new certificates by sending lighttpd pid SIGUSR1 systemctl reload lighttpd ``` ### Example nginx config On an nginx tcp load-balancer you can use the `ssl_preread` module to map a different port for acme-tls requests than for e.g. HTTP/2 or HTTP/1.1 requests. Your config should look something like this: ```nginx stream { map $ssl_preread_alpn_protocols $tls_port { ~\bacme-tls/1\b 10443; default 443; } server { listen 443; listen [::]:443; proxy_pass 10.13.37.42:$tls_port; ssl_preread on; } } ``` That way https requests are forwarded to port 443 on the backend server, and acme-tls/1 requests are forwarded to port 10443. In the future nginx might support internal routing based on custom ALPNs, but for now you'll have to use a custom responder for the alpn verification certificates (see below). ### Example responder I hacked together a simple responder in Python, it might not be the best, but it works for me: ```python #!/usr/bin/env python3 import ssl import socketserver import threading import re import os ALPNDIR="/etc/dehydrated/alpn-certs" PROXY_PROTOCOL=False FALLBACK_CERTIFICATE="/etc/ssl/certs/ssl-cert-snakeoil.pem" FALLBACK_KEY="/etc/ssl/private/ssl-cert-snakeoil.key" class ThreadedTCPServer(socketserver.ThreadingMixIn, socketserver.TCPServer): pass class ThreadedTCPRequestHandler(socketserver.BaseRequestHandler): def create_context(self, certfile, keyfile, first=False): ssl_context = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH) ssl_context.set_ciphers('ECDHE+AESGCM') ssl_context.set_alpn_protocols(["acme-tls/1"]) ssl_context.options |= ssl.OP_NO_TLSv1 | ssl.OP_NO_TLSv1_1 if first: ssl_context.set_servername_callback(self.load_certificate) ssl_context.load_cert_chain(certfile=certfile, keyfile=keyfile) return ssl_context def load_certificate(self, sslsocket, sni_name, sslcontext): print("Got request for %s" % sni_name) if not re.match(r'^(([a-zA-Z]{1})|([a-zA-Z]{1}[a-zA-Z]{1})|([a-zA-Z]{1}[0-9]{1})|([0-9]{1}[a-zA-Z]{1})|([a-zA-Z0-9][-_.a-zA-Z0-9]{0,61}[a-zA-Z0-9]))\.([a-zA-Z]{2,13}|[a-zA-Z0-9-]{2,30}.[a-zA-Z]{2,3})$', sni_name): return certfile = os.path.join(ALPNDIR, "%s.crt.pem" % sni_name) keyfile = os.path.join(ALPNDIR, "%s.key.pem" % sni_name) if not os.path.exists(certfile) or not os.path.exists(keyfile): return sslsocket.context = self.create_context(certfile, keyfile) def handle(self): if PROXY_PROTOCOL: buf = b"" while b"\r\n" not in buf: buf += self.request.recv(1) ssl_context = self.create_context(FALLBACK_CERTIFICATE, FALLBACK_KEY, True) newsock = ssl_context.wrap_socket(self.request, server_side=True) if __name__ == "__main__": HOST, PORT = "0.0.0.0", 10443 server = ThreadedTCPServer((HOST, PORT), ThreadedTCPRequestHandler, bind_and_activate=False) server.allow_reuse_address = True try: server.server_bind() server.server_activate() server.serve_forever() except: server.shutdown() ``` dehydrated-0.7.2/docs/domains_txt.md0000644000175000017500000000670515012216410017302 0ustar lukas2511lukas2511## domains.txt dehydrated uses the file `domains.txt` as configuration for which certificates should be requested. The file should have the following format: ```text example.org example.com www.example.com example.net www.example.net wiki.example.net ``` This states that there are the following certificates: * `example.org` without any *alternative names* * `example.com` with an *alternative name* of `www.example.com` * `example.net` with the *alternative names*: `www.example.net` and `wiki.example.net` ### Aliases You can define an *alias* for your certificate which will (instead of the primary domain) be used as the directory name under your `CERTDIR` and for a per-certificate lookup. This is done using the `>` character. This allows multiple certificates with identical sets of domains but different configuration to exist. Here is an example of using an *alias* called `certalias` for creating the certificate for `example.net` with *alternative names* `www.example.net` and `wiki.example.net`. The certificate will be stored in the directory `certalias` under your `CERTDIR`. ```text example.net www.example.net wiki.example.net > certalias ``` This allows to set per certificates options. The options you can change are explained in [Per Certificate Config](per-certificate-config.md). If you want to create different certificate types for the same domain you can use: ```text *.service.example.org service.example.org > star_service_example_org_rsa *.service.example.org service.example.org > star_service_example_org_ecdsa ``` Then add a config file `certs/star_service_example_org_rsa/config` with the value ``` KEY_ALGO="rsa" ``` or respectively ``` KEY_ALGO="ecdsa" ``` ### Wildcards Support for wildcards was added by the ACME v2 protocol. Certificates with a wildcard domain as the first (or only) name require an *alias* to be set. *Aliases* can't start with `*.`. For example to create the wildcard for `*.service.example.com` your `domains.txt` could use the *alias* method like this: ```text *.service.example.com > star_service_example_com ``` This creates a wildcard certificate for only `*.service.example.com` and will store it in the directory `star_service_example_com` under your `CERTDIR`. As a note this certificate will **NOT** be valid for `service.example.com` but only for `*.service.example.com`. So it would, for example, be valid for `foo.service.example.com`. Another way to create it is using *alternative names*. For example your `domains.txt` could do this: ```text service.example.com *.service.example.com eggs.example.com *.ham.example.com ``` This creates two certificates one for `service.example.com` with an *alternative name* of `*.service.example.com` and a second certificate for `eggs.example.com` with an *alternative name* of `*.ham.example.com`. **Note:** The first certificate is valid for both `service.example.com` and for `*.service.example.com` which can be a useful way to create wildcard certificates. ### Drop-in directory If a directory named `domains.txt.d` exists in the same location as `domains.txt`, the contents of `*.txt` files in that directory are appended to the list of domains, in alphabetical order of the filenames. This is useful for automation, as it doesn't require editing an existing file to add new domains. Warning: Behaviour of this might change as the naming between `domains.txt.d` and the `DOMAINS_D` config variable (which is used for per-certificate configuration) is a bit confusing. dehydrated-0.7.2/docs/dns-verification.md0000644000175000017500000000354715012216410020216 0ustar lukas2511lukas2511### dns-01 challenge This script also supports the new `dns-01`-type verification. This type of verification requires you to be able to create a specific `TXT` DNS record for each hostname included in the certificate. You need a hook script that deploys the challenge to your DNS server. The hook script (indicated in the config file or the `--hook/-k` command line argument) gets four arguments: $1 an operation name (`clean_challenge`, `deploy_challenge`, `deploy_cert`, `invalid_challenge` or `request_failure`) and some operands for that. For `deploy_challenge` $2 is the domain name for which the certificate is required, $3 is a "challenge token" (which is not needed for dns-01), and $4 is a token which needs to be inserted in a TXT record for the domain. Typically, you will need to split the subdomain name in two, the subdomain name and the domain name separately. For example, for "my.example.com", you'll need "my" and "example.com" separately. You then have to prefix "_acme-challenge." before the subdomain name, as in "_acme-challenge.my" and set a TXT record for that on the domain (e.g. "example.com") which has the value supplied in $4 ``` _acme-challenge IN TXT $4 _acme-challenge.my IN TXT $4 ``` That could be done manually (as most providers don't have a DNS API), by having your hook script echo $1, $2 and $4 and then wait (`read -s -r -e < /dev/tty`) - give it a little time to get into their DNS system. Usually providers give you a boxes to put "_acme-challenge.my" and the token value in, and a dropdown to choose the record type, TXT. Or when you do have a DNS API, pass the details accordingly to achieve the same thing. You can delete the TXT record when called with operation `clean_challenge`, when $2 is also the domain name. Here are some examples: [Examples for DNS-01 hooks](https://github.com/dehydrated-io/dehydrated/wiki) dehydrated-0.7.2/docs/hook_chain.md0000644000175000017500000000551415012216410017050 0ustar lukas2511lukas2511# HOOK_CHAIN If you want to deploy (and clean) all challenges for a single certificate in one hook call you can use `HOOK_CHAIN=yes` in your config. Calls to your hook script change in a way that instead of having only X parameters on deploy_challenge and clean_challenge it will have Y*X parameters, where Y is the number of domains in your cert, and you'll have to iterate over a set of X parameters at a time in your hook script. See below for an example on how the calls change: ### HOOK_CHAIN="no" (default behaviour) ``` # INFO: Using main config file /etc/dehydrated/config Processing lukas.im with alternative names: www.lukas.im + Checking domain name(s) of existing cert... unchanged. + Checking expire date of existing cert... + Valid till Jul 7 20:54:00 2016 GMT (Longer than 30 days). Ignoring because renew was forced! + Signing domains... + Generating private key... + Generating signing request... + Requesting challenge for lukas.im... + Requesting challenge for www.lukas.im... HOOK: deploy_challenge lukas.im blablabla blablabla.supersecure + Responding to challenge for lukas.im... HOOK: clean_challenge lukas.im blablabla blablabla.supersecure + Challenge is valid! HOOK: deploy_challenge www.lukas.im blublublu blublublu.supersecure + Responding to challenge for www.lukas.im... HOOK: clean_challenge www.lukas.im blublublu blublublu.supersecure + Challenge is valid! + Requesting certificate... + Checking certificate... + Done! + Creating fullchain.pem... HOOK: deploy_cert lukas.im /etc/dehydrated/certs/lukas.im/privkey.pem /etc/dehydrated/certs/lukas.im/cert.pem /etc/dehydrated/certs/lukas.im/fullchain.pem /etc/dehydrated/certs/lukas.im/chain.pem 1460152442 + Done! ``` ### HOOK_CHAIN="yes" ``` # INFO: Using main config file /etc/dehydrated/config Processing lukas.im with alternative names: www.lukas.im + Checking domain name(s) of existing cert... unchanged. + Checking expire date of existing cert... + Valid till Jul 7 20:52:00 2016 GMT (Longer than 30 days). Ignoring because renew was forced! + Signing domains... + Generating private key... + Generating signing request... + Requesting challenge for lukas.im... + Requesting challenge for www.lukas.im... HOOK: deploy_challenge lukas.im blablabla blablabla.supersecure www.lukas.im blublublu blublublu.supersecure + Responding to challenge for lukas.im... + Challenge is valid! + Responding to challenge for www.lukas.im... + Challenge is valid! HOOK: clean_challenge lukas.im blablabla blablabla.supersecure www.lukas.im blublublu blublublu.supersecure + Requesting certificate... + Checking certificate... + Done! + Creating fullchain.pem... HOOK: deploy_cert lukas.im /etc/dehydrated/certs/lukas.im/privkey.pem /etc/dehydrated/certs/lukas.im/cert.pem /etc/dehydrated/certs/lukas.im/fullchain.pem /etc/dehydrated/certs/lukas.im/chain.pem 1460152408 + Done! ``` dehydrated-0.7.2/docs/staging.md0000644000175000017500000000104215012216410016372 0ustar lukas2511lukas2511# Staging Let’s Encrypt has stringent rate limits in place. If you start testing using the production endpoint (which is the default), you will quickly hit these limits and find yourself locked out. To avoid this, please set the CA property to the Let’s Encrypt staging server URL in your config file: ```bash CA="https://acme-staging-v02.api.letsencrypt.org/directory" ``` Alternatively you can define the CA using the CLI argument `--ca letsencrypt-test` (`letsencrypt-test` is an integrated preset-CA corresponding to the URL above). dehydrated-0.7.2/docs/ecc.md0000644000175000017500000000025615012216410015476 0ustar lukas2511lukas2511### Elliptic Curve Cryptography (ECC) This script also supports certificates with Elliptic Curve public keys! Simply set the `KEY_ALGO` variable in one of the config files. dehydrated-0.7.2/docs/examples/0000755000175000017500000000000015012216410016235 5ustar lukas2511lukas2511dehydrated-0.7.2/docs/examples/config0000644000175000017500000001175015012216410017431 0ustar lukas2511lukas2511######################################################## # This is the main config file for dehydrated # # # # This file is looked for in the following locations: # # $SCRIPTDIR/config (next to this script) # # /usr/local/etc/dehydrated/config # # /etc/dehydrated/config # # ${PWD}/config (in current working-directory) # # # # Default values of this config are in comments # ######################################################## # Which user should dehydrated run as? This will be implicitly enforced when running as root #DEHYDRATED_USER= # Which group should dehydrated run as? This will be implicitly enforced when running as root #DEHYDRATED_GROUP= # Resolve names to addresses of IP version only. (curl) # supported values: 4, 6 # default: #IP_VERSION= # URL to certificate authority or internal preset # Presets: letsencrypt, letsencrypt-test, zerossl, buypass, buypass-test, google, google-test # default: letsencrypt #CA="letsencrypt" # Path to old certificate authority # Set this value to your old CA value when upgrading from ACMEv1 to ACMEv2 under a different endpoint. # If dehydrated detects an account-key for the old CA it will automatically reuse that key # instead of registering a new one. # default: https://acme-v01.api.letsencrypt.org/directory #OLDCA="https://acme-v01.api.letsencrypt.org/directory" # Which challenge should be used? Currently http-01, dns-01 and tls-alpn-01 are supported #CHALLENGETYPE="http-01" # Path to a directory containing additional config files, allowing to override # the defaults found in the main configuration file. Additional config files # in this directory needs to be named with a '.sh' ending. # default: #CONFIG_D= # Directory for per-domain configuration files. # If not set, per-domain configurations are sourced from each certificates output directory. # default: #DOMAINS_D= # Base directory for account key, generated certificates and list of domains (default: $SCRIPTDIR -- uses config directory if undefined) #BASEDIR=$SCRIPTDIR # File containing the list of domains to request certificates for (default: $BASEDIR/domains.txt) #DOMAINS_TXT="${BASEDIR}/domains.txt" # Output directory for generated certificates #CERTDIR="${BASEDIR}/certs" # Output directory for alpn verification certificates #ALPNCERTDIR="${BASEDIR}/alpn-certs" # Directory for account keys and registration information #ACCOUNTDIR="${BASEDIR}/accounts" # Output directory for challenge-tokens to be served by webserver or deployed in HOOK (default: /var/www/dehydrated) #WELLKNOWN="/var/www/dehydrated" # Default keysize for private keys (default: 4096) #KEYSIZE="4096" # Path to openssl config file (default: - tries to figure out system default) #OPENSSL_CNF= # Path to OpenSSL binary (default: "openssl") #OPENSSL="openssl" # Extra options passed to the curl binary (default: ) #CURL_OPTS= # Program or function called in certain situations # # After generating the challenge-response, or after failed challenge (in this case altname is empty) # Given arguments: clean_challenge|deploy_challenge altname token-filename token-content # # After successfully signing certificate # Given arguments: deploy_cert domain path/to/privkey.pem path/to/cert.pem path/to/fullchain.pem # # BASEDIR and WELLKNOWN variables are exported and can be used in an external program # default: #HOOK= # Chain clean_challenge|deploy_challenge arguments together into one hook call per certificate (default: no) #HOOK_CHAIN="no" # Minimum days before expiration to automatically renew certificate (default: 32) #RENEW_DAYS="32" # Regenerate private keys instead of just signing new certificates on renewal (default: yes) #PRIVATE_KEY_RENEW="yes" # Create an extra private key for rollover (default: no) #PRIVATE_KEY_ROLLOVER="no" # Which public key algorithm should be used? Supported: rsa, prime256v1 and secp384r1 #KEY_ALGO=secp384r1 # E-mail to use during the registration (default: ) #CONTACT_EMAIL= # Lockfile location, to prevent concurrent access (default: $BASEDIR/lock) #LOCKFILE="${BASEDIR}/lock" # Option to add CSR-flag indicating OCSP stapling to be mandatory (default: no) #OCSP_MUST_STAPLE="no" # Fetch OCSP responses (default: no) #OCSP_FETCH="no" # OCSP refresh interval (default: 5 days) #OCSP_DAYS=5 # Issuer chain cache directory (default: $BASEDIR/chains) #CHAINCACHE="${BASEDIR}/chains" # Automatic cleanup (default: no) #AUTO_CLEANUP="no" # Delete files during automatic cleanup instead of moving to archive (default: no) #AUTO_CLEANUP_DELETE="no" # ACME API version (default: auto) #API=auto # Preferred issuer chain (default: -> uses default chain) #PREFERRED_CHAIN= # Request certificate with specific profile (default: ) #ACME_PROFILE= # Amount of seconds to wait for processing of order until erroring out (default: 0 => no timeout) #ORDER_TIMEOUT=0 dehydrated-0.7.2/docs/examples/domains.txt0000644000175000017500000000346615012216410020441 0ustar lukas2511lukas2511# Create certificate for 'example.org' with an alternative name of # 'www.example.org'. It will be stored in the directory ${CERT_DIR}/example.org example.org www.example.org # Create certificate for 'example.com' with alternative names of # 'www.example.com' & 'wiki.example.com'. It will be stored in the directory # ${CERT_DIR}/example.com example.com www.example.com wiki.example.com # Using the alias 'certalias' create certificate for 'example.net' with # alternate name 'www.example.net' and store it in the directory # ${CERTDIR}/certalias example.net www.example.net > certalias # Using the alias 'service_example_com' create a wildcard certificate for # '*.service.example.com' and store it in the directory # ${CERTDIR}/service_example_com # NOTE: It is NOT a certificate for 'service.example.com' *.service.example.com > service_example_com # Using the alias 'star_service_example_org' create a wildcard certificate for # '*.service.example.org' with an alternative name of `service.example.org' # and store it in the directory ${CERTDIR}/star_service_example_org # NOTE: It is a certificate for 'service.example.org' *.service.example.org service.example.org > star_service_example_org # Optionally you can also append the certificate algorithm here to create # multiple certificate types for the same domain. # # This allows to set per certificates options. How to do this is # explained in [domains.txt documentation](domains_txt.md). # *.service.example.org service.example.org > star_service_example_org_rsa *.service.example.org service.example.org > star_service_example_org_ecdsa # Create a certificate for 'service.example.net' with an alternative name of # '*.service.example.net' (which is a wildcard domain) and store it in the # directory ${CERTDIR}/service.example.net service.example.net *.service.example.net dehydrated-0.7.2/docs/examples/hook.sh0000755000175000017500000001710115012216410017534 0ustar lukas2511lukas2511#!/usr/bin/env bash deploy_challenge() { local DOMAIN="${1}" TOKEN_FILENAME="${2}" TOKEN_VALUE="${3}" # This hook is called once for every domain that needs to be # validated, including any alternative names you may have listed. # # Parameters: # - DOMAIN # The domain name (CN or subject alternative name) being # validated. # - TOKEN_FILENAME # The name of the file containing the token to be served for HTTP # validation. Should be served by your web server as # /.well-known/acme-challenge/${TOKEN_FILENAME}. # - TOKEN_VALUE # The token value that needs to be served for validation. For DNS # validation, this is what you want to put in the _acme-challenge # TXT record. For HTTP validation it is the value that is expected # be found in the $TOKEN_FILENAME file. # Simple example: Use nsupdate with local named # printf 'server 127.0.0.1\nupdate add _acme-challenge.%s 300 IN TXT "%s"\nsend\n' "${DOMAIN}" "${TOKEN_VALUE}" | nsupdate -k /var/run/named/session.key } clean_challenge() { local DOMAIN="${1}" TOKEN_FILENAME="${2}" TOKEN_VALUE="${3}" # This hook is called after attempting to validate each domain, # whether or not validation was successful. Here you can delete # files or DNS records that are no longer needed. # # The parameters are the same as for deploy_challenge. # Simple example: Use nsupdate with local named # printf 'server 127.0.0.1\nupdate delete _acme-challenge.%s TXT "%s"\nsend\n' "${DOMAIN}" "${TOKEN_VALUE}" | nsupdate -k /var/run/named/session.key } sync_cert() { local KEYFILE="${1}" CERTFILE="${2}" FULLCHAINFILE="${3}" CHAINFILE="${4}" REQUESTFILE="${5}" # This hook is called after the certificates have been created but before # they are symlinked. This allows you to sync the files to disk to prevent # creating a symlink to empty files on unexpected system crashes. # # This hook is not intended to be used for further processing of certificate # files, see deploy_cert for that. # # Parameters: # - KEYFILE # The path of the file containing the private key. # - CERTFILE # The path of the file containing the signed certificate. # - FULLCHAINFILE # The path of the file containing the full certificate chain. # - CHAINFILE # The path of the file containing the intermediate certificate(s). # - REQUESTFILE # The path of the file containing the certificate signing request. # Simple example: sync the files before symlinking them # sync "${KEYFILE}" "${CERTFILE}" "${FULLCHAINFILE}" "${CHAINFILE}" "${REQUESTFILE}" } deploy_cert() { local DOMAIN="${1}" KEYFILE="${2}" CERTFILE="${3}" FULLCHAINFILE="${4}" CHAINFILE="${5}" TIMESTAMP="${6}" # This hook is called once for each certificate that has been # produced. Here you might, for instance, copy your new certificates # to service-specific locations and reload the service. # # Parameters: # - DOMAIN # The primary domain name, i.e. the certificate common # name (CN). # - KEYFILE # The path of the file containing the private key. # - CERTFILE # The path of the file containing the signed certificate. # - FULLCHAINFILE # The path of the file containing the full certificate chain. # - CHAINFILE # The path of the file containing the intermediate certificate(s). # - TIMESTAMP # Timestamp when the specified certificate was created. # Simple example: Copy file to nginx config # cp "${KEYFILE}" "${FULLCHAINFILE}" /etc/nginx/ssl/; chown -R nginx: /etc/nginx/ssl # systemctl reload nginx } deploy_ocsp() { local DOMAIN="${1}" OCSPFILE="${2}" TIMESTAMP="${3}" # This hook is called once for each updated ocsp stapling file that has # been produced. Here you might, for instance, copy your new ocsp stapling # files to service-specific locations and reload the service. # # Parameters: # - DOMAIN # The primary domain name, i.e. the certificate common # name (CN). # - OCSPFILE # The path of the ocsp stapling file # - TIMESTAMP # Timestamp when the specified ocsp stapling file was created. # Simple example: Copy file to nginx config # cp "${OCSPFILE}" /etc/nginx/ssl/; chown -R nginx: /etc/nginx/ssl # systemctl reload nginx } unchanged_cert() { local DOMAIN="${1}" KEYFILE="${2}" CERTFILE="${3}" FULLCHAINFILE="${4}" CHAINFILE="${5}" # This hook is called once for each certificate that is still # valid and therefore wasn't reissued. # # Parameters: # - DOMAIN # The primary domain name, i.e. the certificate common # name (CN). # - KEYFILE # The path of the file containing the private key. # - CERTFILE # The path of the file containing the signed certificate. # - FULLCHAINFILE # The path of the file containing the full certificate chain. # - CHAINFILE # The path of the file containing the intermediate certificate(s). } invalid_challenge() { local DOMAIN="${1}" RESPONSE="${2}" # This hook is called if the challenge response has failed, so domain # owners can be aware and act accordingly. # # Parameters: # - DOMAIN # The primary domain name, i.e. the certificate common # name (CN). # - RESPONSE # The response that the verification server returned # Simple example: Send mail to root # printf "Subject: Validation of ${DOMAIN} failed!\n\nOh noez!" | sendmail root } request_failure() { local STATUSCODE="${1}" REASON="${2}" REQTYPE="${3}" HEADERS="${4}" # This hook is called when an HTTP request fails (e.g., when the ACME # server is busy, returns an error, etc). It will be called upon any # response code that does not start with '2'. Useful to alert admins # about problems with requests. # # Parameters: # - STATUSCODE # The HTML status code that originated the error. # - REASON # The specified reason for the error. # - REQTYPE # The kind of request that was made (GET, POST...) # - HEADERS # HTTP headers returned by the CA # Simple example: Send mail to root # printf "Subject: HTTP request failed failed!\n\nA http request failed with status ${STATUSCODE}!" | sendmail root } generate_csr() { local DOMAIN="${1}" CERTDIR="${2}" ALTNAMES="${3}" # This hook is called before any certificate signing operation takes place. # It can be used to generate or fetch a certificate signing request with external # tools. # The output should be just the certificate signing request formatted as PEM. # # Parameters: # - DOMAIN # The primary domain as specified in domains.txt. This does not need to # match with the domains in the CSR, it's basically just the directory name. # - CERTDIR # Certificate output directory for this particular certificate. Can be used # for storing additional files. # - ALTNAMES # All domain names for the current certificate as specified in domains.txt. # Again, this doesn't need to match with the CSR, it's just there for convenience. # Simple example: Look for pre-generated CSRs # if [ -e "${CERTDIR}/pre-generated.csr" ]; then # cat "${CERTDIR}/pre-generated.csr" # fi } startup_hook() { # This hook is called before the cron command to do some initial tasks # (e.g. starting a webserver). : } exit_hook() { local ERROR="${1:-}" # This hook is called at the end of the cron command and can be used to # do some final (cleanup or other) tasks. # # Parameters: # - ERROR # Contains error message if dehydrated exits with error } HANDLER="$1"; shift if [[ "${HANDLER}" =~ ^(deploy_challenge|clean_challenge|sync_cert|deploy_cert|deploy_ocsp|unchanged_cert|invalid_challenge|request_failure|generate_csr|startup_hook|exit_hook)$ ]]; then "$HANDLER" "$@" fi dehydrated-0.7.2/docs/per-certificate-config.md0000644000175000017500000000135015012216410021251 0ustar lukas2511lukas2511# Config on per-certificate base dehydrated allows a few configuration variables to be set on a per-certificate base. To use this feature create a `config` file in the certificates output directory (e.g. `certs/example.org/config`). Currently supported options: - PRIVATE_KEY_RENEW - PRIVATE_KEY_ROLLOVER - KEY_ALGO - KEYSIZE - OCSP_MUST_STAPLE - OCSP_FETCH - OCSP_DAYS - CHALLENGETYPE - HOOK - HOOK_CHAIN - WELLKNOWN - OPENSSL_CNF - RENEW_DAYS - PREFERRED_CHAIN ## DOMAINS_D If `DOMAINS_D` is set, dehydrated will use it for your per-certificate configurations. Instead of `certs/example.org/config` it will look for a configuration under `DOMAINS_D/example.org`. If an alias is set, it will be used instead of the primary domain name. dehydrated-0.7.2/docs/acme-v1.md0000644000175000017500000000325515012216410016177 0ustar lukas2511lukas2511## (Future) Removal of API version 1 The ACME API version 1 was never really standardized and was only supported by Let's Encrypt. Even though the protocol specification was public, it wasn't really friendly to be integrated into existing CA systems so initial adoption was basically non-existant. ACME version 2 is being designed to overcome these issues by becoming an official IETF standard and supporting a more traditional approach of account and order management in the backend, making it friendlier to integrate into existing systems centered around those. It has since become a semi-stable IETF standard draft which only ever got two breaking changes, Content-Type enforcement and `POST-as-GET`, the latter being announced in October 2018 to be enforced by November 2019. See https://datatracker.ietf.org/wg/acme/documents/ for a better insight into the draft and its changes. Next to backend changes that many users won't really care about ACME v2 has all of the features ACME v1 had, but also some additional new features like e.g. support for [wildcard certificates](domains_txt.md#wildcards). Since ACME v2 is basically to be considered stable and ACME v1 has no real benefits over v2, there doesn't seem to be much of a reason to keep the old protocol around, but since there actually are a few Certificate Authorities and resellers that implemented the v1 protocol and didn't yet make the change to v2, so dehydrated still supports the old protocol for now. Please keep in mind that support for the old ACME protocol version 1 might get removed at any point of bigger inconvenience, e.g. on code changes that would require a lot of work or ugly workarounds to keep both versions supported. dehydrated-0.7.2/docs/troubleshooting.md0000644000175000017500000000673515012216410020203 0ustar lukas2511lukas2511# Troubleshooting Generally if the following information doesn't provide a solution to your problem please take a look at existing issues (search for keywords) before creating a new one. ## "No registration exists matching provided key" You probably changed from staging-CA to production-CA (or the other way). Currently dehydrated doesn't detect a missing registration on the selected CA, the current workaround is to move `private_key.pem` (and, if you care, `private_key.json`) out of the way so the scripts generates and registers a new one. This will hopefully be fixed in the future. ## "Error creating new cert :: Too many certificates already issued for: [...]" This is not an issue with dehydrated but an API limit with boulder (the ACME server). At the time of writing this you can only create 5 certificates per domain in a sliding window of 7 days. ## "Certificate request has 123 names, maximum is 100." This also is an API limit from boulder, you are requesting to sign a certificate with way too many domains. ## Invalid challenges There are a few factors that could result in invalid challenges. If you are using HTTP validation make sure that the path you have configured with WELLKNOWN is readable under your domain. To test this create a file (e.g. `test.txt`) in that directory and try opening it with your browser: `http://example.org/.well-known/acme-challenge/test.txt`. Note that if you have an IPv6 address, the challenge connection will be on IPv6. Be sure that you test HTTP connections on both IPv4 and IPv6. Checking the test file in your browser is often not sufficient because the browser just fails over to IPv4. If you get any error you'll have to fix your web server configuration. ## DNS invalid challenge since dehydrated 0.6.0 / Why are DNS challenges deployed first and verified later? Since Let's Encrypt (and in general the ACMEv2 protocol) now supports wildcard domains there is a situation where DNS caching can become a problem. If somebody wants to validate a certificate with `example.org` and `*.example.org` there are two tokens that have to be deployed on `_acme-challenge.example.org`. If dehydrated would deploy and verify each token on its own the CA would cache the first token on `_acme-challenge.example.org` and the next challenge would simply fail. Let's Encrypt uses your DNS TTL with a max limit of 5 minutes, but this doesn't seem to be part of the ACME protocol, just some LE specific configuration, so with other CAs and certain DNS providers who don't allow low TTLs this could potentially take hours. Since dehydrated now deploys all challenges first that no longer is a problem. The CA will query and cache both challenges, and both authorizations can be validated. Some hook-scripts were written in a way that erases the old TXT record rather than adding a new entry, those should be (and many of them already have been) fixed. There are certain DNS providers which really only allow one TXT record on a domain. This is really odd and you should probably contact your DNS provider and ask them to fix this. If for whatever reason you can't switch DNS providers and your DNS provider only supports one TXT record and doesn't want to fix that you could try splitting your certificate into multiple certificates and add a sleep in the `deploy_cert` hook. If you can't do that or really don't want to please leave a comment on https://github.com/lukas2511/dehydrated/issues/554, if many people are having this unfixable problem I might try to implement a workaround. dehydrated-0.7.2/docs/man/0000755000175000017500000000000015012216410015172 5ustar lukas2511lukas2511dehydrated-0.7.2/docs/man/dehydrated.10000644000175000017500000001304515012216410017374 0ustar lukas2511lukas2511.TH DEHYDRATED 1 2018-01-13 "Dehydrated ACME Client" .SH NAME dehydrated \- ACME client implemented as a shell-script .SH SYNOPSIS .B dehydrated [\fBcommand\fR [\fBargument\fR]] [\fBargument\fR [\fBargument\fR]] .IR ... .SH DESCRIPTION A client for ACME-based Certificate Authorities, such as LetsEncrypt. It can be used to request and obtain TLS certificates from an ACME-based certificate authority. Before any certificates can be requested, Dehydrated needs to acquire an account with the Certificate Authorities. Optionally, an email address can be provided. It will be used to e.g. notify about expiring certificates. You will usually need to accept the Terms of Service of the CA. Dehydrated will notify if no account is configured. Run with \fB--register --accept-terms\fR to create a new account. Next, all domain names must be provided in domains.txt. The format is line based: If the file contains two lines "example.com" and "example.net", dehydrated will request two certificate, one for "example.com" and the other for "example.net". A single line containing "example.com example.net" will request a single certificate valid for both "example.net" and "example.com" through the \fISubject Alternative Name\fR (SAN) field. For the next step, one way of verifying domain name ownership needs to be configured. Dehydrated implements \fIhttp-01\fR and \fIdns-01\fR verification. The \fIhttp-01\fR verification provides proof of ownership by providing a challenge token. In order to do that, the directory referenced in the \fIWELLKNOWN\fR config variable needs to be exposed at \fIhttp://{domain}/.well-known/acme-challenge/\fR, where {domain} is every domain name specified in \fIdomains.txt\fR. Dehydrated does not provide its own challenge responder, but relies on an existing web server to provide the challenge response. See \fIwellknown.md\fR for configuration examples of popular web servers. The \fIdns-01\fR verification works by providing a challenge token through DNS. This is especially interesting for hosts that cannot be exposed to the public Internet. Because adding records to DNS zones is oftentimes highly specific to the software or the DNS provider at hand, there are many third party hooks available for dehydrated. See \fIdns-verification.md\fR for hooks for popular DNS servers and DNS hosters. Finally, the certificates need to be requested and updated on a regular basis. This can happen through a cron job or a timer. Initially, you may enforce this by invoking \fIdehydrated -c\fR manually. After a successful run, certificates are stored in \fI/etc/dehydrated/certs/{domain}\fR, where {domain} is the domain name in the first column of \fIdomains.txt\fR. .SH OPTIONS .BR Commands .TP .BR \-\-version ", " \-v Print version information .TP .BR \-\-register Register account key .TP .BR \-\-account Update account contact information .TP .BR \-\-cron ", " \-c Sign/renew non\-existent/changed/expiring certificates. .TP .BR \-\-signcsr ", " \-s " " \fIpath/to/csr.pem\fR Sign a given CSR, output CRT on stdout (advanced usage) .TP .BR \-\-revoke ", " \-r " " \fIpath/to/cert.pem\fR Revoke specified certificate .TP .BR \-\-cleanup ", " \-gc Move unused certificate files to archive directory .TP .BR \-\-help ", " \-h Show help text .TP .BR \-\-env ", " \-e Output configuration variables for use in other scripts .PP .BR Parameters .TP .BR \-\-accept\-terms Accept CAs terms of service .TP .BR \-\-full\-chain ", " \-fc Print full chain when using \fB\-\-signcsr\fR .TP .BR \-\-ipv4 ", " \-4 Resolve names to IPv4 addresses only .TP .BR \-\-ipv6 ", " \-6 Resolve names to IPv6 addresses only .TP .BR \-\-domain ", " \-d " " \fIdomain.tld\fR Use specified domain name(s) instead of domains.txt entry (one certificate!) .TP .BR \-\-keep\-going ", " \-g Keep going after encountering an error while creating/renewing multiple certificates in cron mode .TP .BR \-\-force ", " \-x Force certificate renewal even if it is not due to expire within RENEW_DAYS .TP .BR \-\-no\-lock ", " \-n Don't use lockfile (potentially dangerous!) .TP .BR \-\-lock\-suffix " " \fIexample.com\fR Suffix lockfile name with a string (useful for use with \-d) .TP .BR \-\-ocsp Sets option in CSR indicating OCSP stapling to be mandatory .TP .BR \-\-privkey ", " \-p " " \fIpath/to/key.pem\fR Use specified private key instead of account key (useful for revocation) .TP .BR \-\-config ", " \-f " " \fIpath/to/config\fR Use specified config file .TP .BR \-\-hook ", " \-k " " \fIpath/to/hook.sh\fR Use specified script for hooks .TP .BR \-\-out ", " \-o " " \fIcerts/directory\fR Output certificates into the specified directory .TP .BR \-\-challenge ", " \-t " " \fI[http\-01|dns\-01]\fR Which challenge should be used? Currently http\-01 and dns\-01 are supported .TP .BR \-\-algo ", " \-a " " \fI[rsa|prime256v1|secp384r1]\fR Which public key algorithm should be used? Supported: rsa, prime256v1 and secp384r1 .SH DIAGNOSTICS The program exits 0 if everything was fine, 1 if an error occurred. .SH BUGS Please report any bugs that you may encounter at the project web site .UR https://github.com/dehydrated-io/dehydrated/issues .UE . .SH AUTHOR Dehydrated was written by Lukas Schauer. This man page was contributed by Daniel Molkentin. .SH COPYRIGHT Copyright 2015-2018 by Lukas Schauer and the respective contributors. Provided under the MIT License. See the LICENSE file that accompanies the distribution for licensing information. .SH SEE ALSO Full documentation along with configuration examples are provided in the \fIdocs\fR directory of the distribution, or at .UR https://github.com/dehydrated-io/dehydrated/tree/master/docs .UE .