#!/bin/sh # Download TLS certificates of hosts specified in the `hosts` file. set -o errexit # (-e) exit immediately if any command has a non-zero exit status set -o nounset # (-u) don't accept undefined variables #set -o xtrace # for debugging # Go where this script is. cd "$(dirname "$0")" || exit # Create certs directory, if it does not exist. mkdir -p certs # Define function for fetching a certificate. fetch_cert() ( hp="$1" # host and port timeout="${2:-}" torsocks="${3:-}" # For .onion domains, connect using Tor. if [ -z "${hp##*.onion:*}" ]; then torsocks='torsocks' fi # If a Tor connection was requested and torsocks is not installed, return. if [ "$torsocks" != '' ] && ! command -v torsocks >/dev/null; then >&2 echo "torsocks not available ($hp)" return fi response=$($timeout $torsocks openssl s_client -connect "$hp" /dev/null \ | sed -ne '/-BEGIN CERTIFICATE-/,/-END CERTIFICATE-/p') # If no response and Tor was not used, try again. if [ -z "$response" ] && [ -z "$torsocks" ]; then sleep 5 response=$($timeout openssl s_client -connect "$hp" /dev/null \ | sed -ne '/-BEGIN CERTIFICATE-/,/-END CERTIFICATE-/p') fi echo "$response" ) # Loop through all hosts. while read -r host; do printf "%s" "$host" port='1965' # default Gemini port # Check if host contains square brackets: # [IPv4], [IPv6], [IPv4]:port or [IPv6]:port. if expr "$host" : '^\[.*\]' >/dev/null; then if expr "$host" : '^\[.*\]\:\([0-9]*\)$' >/dev/null; then port=$(expr "$host" : '^\[.*\]\:\([0-9]*\)$') fi host=$(expr "$host" : '^\[\(.*\)\]') # No square brackets, just host:port. # If $host contains only a single ":", then it is not an IPv6 address. elif [ "$(echo "$host" | tr -dc ':' | wc -c)" = 1 ]; then port=$(echo "$host" | cut -d ':' -f 2) host=$(echo "$host" | cut -d ':' -f 1) fi # Hostname to punycode. host=$(echo "$host" | idn --allow-unassigned) # Hostname to lowercase. host=$(echo "$host" | tr '[:upper:]' '[:lower:]') # Generate host_and_port string. if [ -z "${host##*:*}" ]; then # If host is an IPv6 address, add brackets around it. host_and_port="[$host]:$port" else host_and_port="$host:$port" fi # Get cert. cert=$(fetch_cert "$host_and_port" 'timeout 10') if [ -z "$cert" ]; then >&2 echo # empty line >&2 echo "$host_and_port - connection failed" fi # If "tor" option is used, then connect again via Tor, # to check if we get the same cert from a different network perspective. if [ "${1:-}" = 'tor' ] && [ -n "${host##*.onion}" ]; then # If torsocks is not installed, return. if ! command -v torsocks >/dev/null; then >&2 echo "torsocks not available ($host_and_port)" exit 1 fi cert_via_tor=$(fetch_cert "$host_and_port" 'timeout 25' 'torsocks') if [ -z "$cert_via_tor" ]; then [ -n "$cert" ] && >&2 echo # output empty line to stderr if cert was downloaded without Tor >&2 echo "$host_and_port - Tor connection failed" elif [ -n "$cert" ] && [ "$cert" != "$cert_via_tor" ]; then >&2 echo "$host_and_port - Tor VERIFICATION FAILED (certs don't match)!!!" # In this case, don't save any certificate to file. # Output both certificates to stderr instead. >&2 echo "CERT:" >&2 echo "$cert" >&2 echo "CERT VIA TOR:" >&2 echo "$cert_via_tor" continue else # If direct connection failed and Tor connection succeeded, # use the cert received via Tor. cert="$cert_via_tor" fi fi if [ -n "$cert" ]; then # If we got a cert back, then the host and port were valid, # so they are safe to include in a file name. # Convert from punycode to unicode, if needed. host_and_port=$(echo "$host_and_port" | idn --allow-unassigned --idna-to-unicode) echo "$cert" > "certs/${host_and_port}.pem" printf ' - OK' else printf ' - failed' fi echo # newline sleep 0.3 done < hosts echo OK